From b739976c241f844ccf94c59b3d37bcf1e2c76ca1 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Fri, 1 Sep 2023 12:49:54 +0800 Subject: [PATCH] add lifecycle listeners --- ios/Classes/SwiftPolyvoxFilamentPlugin.swift | 27 ++++++- lib/filament_controller.dart | 42 +++++++--- lib/filament_widget.dart | 85 ++++++++++++-------- 3 files changed, 106 insertions(+), 48 deletions(-) diff --git a/ios/Classes/SwiftPolyvoxFilamentPlugin.swift b/ios/Classes/SwiftPolyvoxFilamentPlugin.swift index e57e03c8..741b7b59 100644 --- a/ios/Classes/SwiftPolyvoxFilamentPlugin.swift +++ b/ios/Classes/SwiftPolyvoxFilamentPlugin.swift @@ -187,6 +187,24 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture createPixelBuffer(width:Int(args[0]), height:Int(args[1])) createDisplayLink() result(self.flutterTextureId) + case "destroyTexture": + if(viewer != nil) { + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Destroy the viewer before destroying the texture", details: nil)) + } else { + + if(self.flutterTextureId != nil) { + self.registry.unregisterTexture(self.flutterTextureId!) + } + self.flutterTextureId = nil + self.pixelBuffer = nil + } + case "destroyViewer": + if(viewer != nil) { + destroy_swap_chain(viewer) + delete_filament_viewer(viewer) + viewer = nil + } + result(true) case "resize": if(viewer == nil) { print("Error: cannot resize before a viewer has been created") @@ -203,6 +221,11 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture print("Resized to \(args[0])x\(args[1])") result(self.flutterTextureId); case "createFilamentViewer": + if(viewer != nil) { + destroy_swap_chain(viewer) + delete_filament_viewer(viewer) + viewer = nil + } let callback = make_resource_loader(loadResource, freeResource, Unmanaged.passUnretained(self).toOpaque()) let args = call.arguments as! [Any] let width = args[0] as! Int64 @@ -212,10 +235,6 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture create_swap_chain(viewer, pixelBufferTextureId, UInt32(width), UInt32(height)) update_viewport_and_camera_projection(viewer, Int32(args[0] as! Int64), Int32(args[1] as! Int64), 1.0) result(unsafeBitCast(viewer, to:Int64.self)) - case "deleteFilamentViewer": - delete_filament_viewer(viewer) - viewer = nil - result(true) case "getAssetManager": let assetManager = get_asset_manager(viewer) result(unsafeBitCast(assetManager, to:Int64.self)) diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index dbb81af1..6c93567f 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -23,11 +23,8 @@ class FilamentController { final _textureIdController = StreamController.broadcast(); Stream get textureId => _textureIdController.stream; - final _onInitRequestedController = StreamController.broadcast(); - Stream get onInitializationRequested => _onInitRequestedController.stream; - - final _initialized = Completer(); - Future get initialized => _initialized.future; + // final _viewerAvailableController = StreamController.broadcast(); + // Stream get viewerAvailable => _viewerAvailableController.stream; late AssetManager _assetManager; @@ -45,9 +42,24 @@ class FilamentController { }; } + final _initialSize = Completer>(); + void setInitialSize(int width, int height) { + _initialSize.complete([width, height]); + } + + /// + /// The process for initializing the Filament layer is as follows: + /// 1) Create a FilamentController + /// 2) Insert a FilamentWidget into the rendering tree + /// 3) Initially, this widget will only contain an empty Container. After the first frame is rendered, the widget itself will automatically call [setInitialSize] with the width/height from its constraints + /// 4) Call [initialize], which will create a texture/viewer and notify the FilamentWidget that the texture is available + /// 5) The FilamentWidget will replace the empty Container with the Texture widget. + /// Future initialize() async { - _onInitRequestedController.add(true); - return _initialized.future; + var initialSize = await _initialSize.future; + var initialWidth = initialSize[0]; + var initialHeight = initialSize[1]; + await createViewer(initialWidth, initialHeight); } Future setRendering(bool render) async { @@ -66,6 +78,17 @@ class FilamentController { _pixelRatio = ratio; } + Future destroyViewer() async { + await _channel.invokeMethod("destroyViewer"); + } + + Future destroyTexture() async { + await _channel.invokeMethod("destroyTexture"); + _textureId = null; + _assetManager = 0; + _textureIdController.add(null); + } + Future createViewer(int width, int height) async { size = ui.Size(width * _pixelRatio, height * _pixelRatio); _textureId = @@ -90,7 +113,6 @@ class FilamentController { await _channel.invokeMethod("updateViewportAndCameraProjection", [size.width.toInt(), size.height.toInt(), 1.0]); - _initialized.complete(true); _assetManager = await _channel.invokeMethod("getAssetManager"); _textureIdController.add(_textureId); } @@ -438,7 +460,7 @@ class FilamentController { .invokeMethod("setRotation", [_assetManager, asset, rads, x, y, z]); } - void hide(FilamentEntity asset, String meshName) async { + Future hide(FilamentEntity asset, String meshName) async { if (await _channel .invokeMethod("hideMesh", [_assetManager, asset, meshName]) != 1) { @@ -446,7 +468,7 @@ class FilamentController { } } - void reveal(FilamentEntity asset, String meshName) async { + Future reveal(FilamentEntity asset, String meshName) async { if (await _channel .invokeMethod("revealMesh", [_assetManager, asset, meshName]) != 1) { diff --git a/lib/filament_widget.dart b/lib/filament_widget.dart index 9b462fe0..1235a2ee 100644 --- a/lib/filament_widget.dart +++ b/lib/filament_widget.dart @@ -4,6 +4,8 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/scheduler.dart'; + import 'dart:async'; import 'filament_controller.dart'; @@ -56,38 +58,58 @@ class FilamentWidget extends StatefulWidget { } class _FilamentWidgetState extends State { - StreamSubscription? _initializationListener; StreamSubscription? _textureIdListener; int? _textureId; bool _resizing = false; - bool _hasViewer = false; + + late final AppLifecycleListener _listener; + AppLifecycleState? _lastState; + + void _handleStateChange(AppLifecycleState state) async { + switch (state) { + case AppLifecycleState.detached: + print("Detached"); + await widget.controller.destroyViewer(); + await widget.controller.destroyTexture(); + break; + case AppLifecycleState.hidden: + print("Hidden"); + _textureId = null; + await widget.controller.destroyViewer(); + await widget.controller.destroyTexture(); + break; + case AppLifecycleState.inactive: + print("Inactive"); + break; + case AppLifecycleState.paused: + print("Paused"); + break; + case AppLifecycleState.resumed: + print("Resumed"); + if (_textureId == null) { + var size = ((context.findRenderObject()) as RenderBox).size; + print("Size after resuming : $size"); + await widget.controller + .createViewer(size.width.toInt(), size.height.toInt()); + } + break; + } + _lastState = state; + } @override void initState() { - _initializationListener = - widget.controller.onInitializationRequested.listen((_) { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { - var size = ((context.findRenderObject()) as RenderBox).size; - print( - "Requesting creation of Filament back-end texture/viewer for viewport size $size"); - await widget.controller - .createViewer(size.width.toInt(), size.height.toInt()); - setState(() { - _hasViewer = true; - }); + _listener = AppLifecycleListener( + onStateChange: _handleStateChange, + ); - _initializationListener!.cancel(); - _initializationListener = null; - - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - var size = ((context.findRenderObject()) as RenderBox).size; - widget.controller.resize(size.width.toInt(), size.height.toInt()); - print("RESIZED IN POST FRAME CALLBACK TO $size"); - }); - setState(() {}); - }); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + var size = ((context.findRenderObject()) as RenderBox).size; + widget.controller.setInitialSize(size.width.toInt(), size.height.toInt()); }); + _textureIdListener = widget.controller.textureId.listen((int? textureId) { + print("Set texture ID to $textureId"); setState(() { _textureId = textureId; }); @@ -98,15 +120,14 @@ class _FilamentWidgetState extends State { @override void dispose() { - _initializationListener?.cancel(); _textureIdListener?.cancel(); + _listener.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return LayoutBuilder(builder: ((context, constraints) { - print("constraints $constraints"); if (_textureId == null) { return Container(color: Colors.transparent); } @@ -121,10 +142,6 @@ class _FilamentWidgetState extends State { width: constraints.maxWidth, child: ResizeObserver( onResized: (Size oldSize, Size newSize) async { - if (!_hasViewer) { - return; - } - print("RESIZE OBSERVER $newSize"); WidgetsBinding.instance.addPostFrameCallback((_) async { setState(() { _resizing = true; @@ -139,15 +156,15 @@ class _FilamentWidgetState extends State { }); }); }, - child: Platform.isLinux - ? _resizing - ? Container() - : Transform( + child: _resizing + ? Container() + : Platform.isLinux + ? Transform( alignment: Alignment.center, transform: Matrix4.rotationX( pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere? child: texture) - : texture)); + : texture)); })); } }