diff --git a/thermion_dart/lib/src/bindings/src/js_interop.dart b/thermion_dart/lib/src/bindings/src/js_interop.dart index 03aba0e7..29832196 100644 --- a/thermion_dart/lib/src/bindings/src/js_interop.dart +++ b/thermion_dart/lib/src/bindings/src/js_interop.dart @@ -103,6 +103,12 @@ Future> withPointerCallback( func.call(onComplete_interopFnPtr.cast()); + + while (!completer.isCompleted) { + _NativeLibrary.instance._execute_queue(); + await Future.delayed(Duration(milliseconds: 1)); + } + var ptr = await completer.future; onComplete_interopFnPtr.dispose(); @@ -120,7 +126,11 @@ Future withBoolCallback( final onComplete_interopFnPtr = callback.addFunction(); func.call(onComplete_interopFnPtr.cast()); - await completer.future; + + while (!completer.isCompleted) { + _NativeLibrary.instance._execute_queue(); + await Future.delayed(Duration(milliseconds: 1)); + } return completer.future; } @@ -134,20 +144,26 @@ Future withFloatCallback( }; var ptr = callback.addFunction(); func.call(ptr); - await completer.future; + while (!completer.isCompleted) { + _NativeLibrary.instance._execute_queue(); + await Future.delayed(Duration(milliseconds: 1)); + } return completer.future; } Future withIntCallback( Function(Pointer>) func) async { - final completer = Completer(); + final completer = Completer(); // ignore: prefer_function_declarations_over_variables void Function(int) callback = (int result) { completer.complete(result); }; var ptr = callback.addFunction(); func.call(ptr); - await completer.future; + while (!completer.isCompleted) { + _NativeLibrary.instance._execute_queue(); + await Future.delayed(Duration(milliseconds: 1)); + } return completer.future; } diff --git a/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart b/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart index 3118d933..57d0d0b2 100644 --- a/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart +++ b/thermion_dart/lib/src/filament/src/implementation/ffi_filament_app.dart @@ -289,14 +289,14 @@ class FFIFilamentApp extends FilamentApp { await renderManager.detachAll(swapChain); await destroySwapChain(swapChain); } + + FilamentApp.instance = null; renderManager.destroy(); await withVoidCallback((requestId, cb) async { Engine_destroyRenderThread(engine, requestId, cb); }); RenderThread_destroy(); - - FilamentApp.instance = null; for (final callback in _onDestroy) { await callback.call(); } diff --git a/thermion_dart/native/src/rendering/RenderThread.cpp b/thermion_dart/native/src/rendering/RenderThread.cpp index 0808969d..f707a9fa 100644 --- a/thermion_dart/native/src/rendering/RenderThread.cpp +++ b/thermion_dart/native/src/rendering/RenderThread.cpp @@ -26,9 +26,15 @@ bool loopExitTimeValid = false; static void mainLoop(void* arg) { auto *rt = static_cast(arg); - if (!rt->mStop) { - rt->iter(); + // If the render thread has been asked to stop, break the loop and exit immediately. + if (rt->mStop) { + emscripten_cancel_main_loop(); + loopExitTime = std::chrono::high_resolution_clock::now(); + loopExitTimeValid = true; + return; } + + rt->iter(); loopExitTime = std::chrono::high_resolution_clock::now(); loopExitTimeValid = true; } @@ -82,7 +88,11 @@ RenderThread::~RenderThread() _tasks.pop_front(); task(); } - #ifndef __EMSCRIPTEN__ + + #ifdef __EMSCRIPTEN__ + // Waiting for the main loop to exit before continuing + pthread_join(t, nullptr); + #else t->join(); delete t; #endif diff --git a/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_web.dart b/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_web.dart index 063edd93..2a0dbc0a 100644 --- a/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_web.dart +++ b/thermion_flutter/thermion_flutter/lib/src/platform/src/thermion_flutter_plugin_web.dart @@ -17,6 +17,7 @@ class ThermionFlutterPluginImpl extends ThermionFlutterPlugin { static final _descriptors = []; static final _destroyed = []; static final _logger = Logger('ThermionFlutterPluginImpl'); + static int? _frameRequestId; static Future loadAsset(String path) async { if (path.startsWith("file://")) { @@ -48,13 +49,37 @@ class ThermionFlutterPluginImpl extends ThermionFlutterPlugin { } _destroyed.clear(); - window.requestAnimationFrame(_tick.toJS); + _frameRequestId = window.requestAnimationFrame(_tick.toJS); + } + + static void _ensureFrameLoopRunning() { + _frameRequestId ??= window.requestAnimationFrame(_tick.toJS); + } + + + static void _resetWebState() { + if (_frameRequestId != null) { + window.cancelAnimationFrame(_frameRequestId!); + _frameRequestId = null; + } + + _stackPtr = null; + swapChain = null; + _descriptors.clear(); + _destroyed.clear(); } static SwapChain? swapChain; @override Future initialize({bool destroySwapchain = true}) async { + if (FilamentApp.instance != null && swapChain != null) { + // Hot reload re-enters initialize without disposing the existing web + // engine. Reuse the live app instead of spawning another em-pthread. + _ensureFrameLoopRunning(); + return swapChain!; + } + HTMLCanvasElement? canvas; // first, try and initialize bindings to see if the user has included thermion_dart.js manually in index.html try { @@ -118,6 +143,10 @@ class ThermionFlutterPluginImpl extends ThermionFlutterPlugin { sharedContext: null, uberArchivePath: options.uberarchivePath); await FFIFilamentApp.create(config: config); + // resetting the web state when the app is destroyed + (FilamentApp.instance as FFIFilamentApp).onDestroy(() async { + _resetWebState(); + }); // Use createSwapChain with nullptr to render to the canvas's default // framebuffer (framebuffer 0). createHeadlessSwapChain creates an offscreen @@ -126,7 +155,7 @@ class ThermionFlutterPluginImpl extends ThermionFlutterPlugin { print("Created 1x1 headless swapchain"); - window.requestAnimationFrame(_tick.toJS); + _ensureFrameLoopRunning(); return swapChain!; }