make ThermionFlutterPlugin static, remove dispose() and add internal listener for ThermionViewer.onDispose

This commit is contained in:
Nick Fisher
2024-06-19 12:57:16 +08:00
parent 8e75555540
commit 31e68df1c5
2 changed files with 90 additions and 89 deletions

View File

@@ -1,103 +1,100 @@
import 'dart:async'; import 'dart:async';
import 'dart:ui';
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
/// ///
/// Handles all platform-specific initialization work necessary to create a /// Handles all platform-specific initialization to create a backing rendering
/// backing rendering surface in a Flutter application. /// surface in a Flutter application and lifecycle listeners to pause rendering
/// Instantiates/wraps a [ThermionViewer], /// when the app is inactive or in the background.
/// Call [createViewer] to create an instance of [ThermionViewer].
/// This is a lightweight singleton that
/// ///
class ThermionFlutterPlugin { class ThermionFlutterPlugin {
ThermionViewer get _viewer => ThermionFlutterPlatform.instance.viewer;
ThermionFlutterPlugin._();
bool _wasRenderingOnInactive = false; static AppLifecycleListener? _appLifecycleListener;
void _handleStateChange(AppLifecycleState state) async { static bool _initializing = false;
await initialized;
static ThermionViewer? _viewer;
static bool _wasRenderingOnInactive = false;
static void _handleStateChange(AppLifecycleState state) async {
if (_viewer == null) {
return;
}
await _viewer!.initialized;
switch (state) { switch (state) {
case AppLifecycleState.detached: case AppLifecycleState.detached:
print("Detached");
if (!_wasRenderingOnInactive) { if (!_wasRenderingOnInactive) {
_wasRenderingOnInactive = _viewer.rendering; _wasRenderingOnInactive = _viewer!.rendering;
} }
await _viewer.setRendering(false); await _viewer!.setRendering(false);
break; break;
case AppLifecycleState.hidden: case AppLifecycleState.hidden:
print("Hidden");
if (!_wasRenderingOnInactive) { if (!_wasRenderingOnInactive) {
_wasRenderingOnInactive = _viewer.rendering; _wasRenderingOnInactive = _viewer!.rendering;
} }
await _viewer.setRendering(false); await _viewer!.setRendering(false);
break; break;
case AppLifecycleState.inactive: case AppLifecycleState.inactive:
print("Inactive");
if (!_wasRenderingOnInactive) { if (!_wasRenderingOnInactive) {
_wasRenderingOnInactive = _viewer.rendering; _wasRenderingOnInactive = _viewer!.rendering;
} }
// on Windows in particular, restoring a window after minimizing stalls the renderer (and the whole application) for a considerable length of time. // on Windows in particular, restoring a window after minimizing stalls the renderer (and the whole application) for a considerable length of time.
// disabling rendering on minimize seems to fix the issue (so I wonder if there's some kind of command buffer that's filling up while the window is minimized). // disabling rendering on minimize seems to fix the issue (so I wonder if there's some kind of command buffer that's filling up while the window is minimized).
await _viewer.setRendering(false); await _viewer!.setRendering(false);
break; break;
case AppLifecycleState.paused: case AppLifecycleState.paused:
print("Paused");
if (!_wasRenderingOnInactive) { if (!_wasRenderingOnInactive) {
_wasRenderingOnInactive = _viewer.rendering; _wasRenderingOnInactive = _viewer!.rendering;
} }
await _viewer.setRendering(false); await _viewer!.setRendering(false);
break; break;
case AppLifecycleState.resumed: case AppLifecycleState.resumed:
print("Resumed"); await _viewer!.setRendering(_wasRenderingOnInactive);
await _viewer.setRendering(_wasRenderingOnInactive);
break; break;
} }
} }
AppLifecycleListener? _appLifecycleListener; static Future<ThermionViewer> createViewer({String? uberArchivePath}) async {
if (_initializing) {
final _initialized = Completer<bool>(); throw Exception("Existing call to createViewer has not completed.");
Future<bool> get initialized => _initialized.future;
bool _initializing = false;
Future<ThermionViewer> initialize({String? uberArchivePath}) async {
_initializing = true;
if (_initialized.isCompleted) {
return ThermionFlutterPlatform.instance.viewer;
} }
await ThermionFlutterPlatform.instance _initializing = true;
.initialize(uberArchivePath: uberArchivePath); _viewer = await ThermionFlutterPlatform.instance
.createViewer(uberArchivePath: uberArchivePath);
_appLifecycleListener = AppLifecycleListener( _appLifecycleListener = AppLifecycleListener(
onStateChange: _handleStateChange, onStateChange: _handleStateChange,
); );
_viewer.initialized; _viewer!.onDispose(() async {
_initialized.complete(true); _viewer = null;
_appLifecycleListener?.dispose();
_appLifecycleListener = null;
});
_initializing = false; _initializing = false;
return ThermionFlutterPlatform.instance.viewer; return _viewer!;
} }
Future<ThermionFlutterTexture?> createTexture( static Future<ThermionFlutterTexture?> createTexture(
int width, int height, int offsetLeft, int offsetRight) async { int width, int height, int offsetLeft, int offsetRight) async {
return ThermionFlutterPlatform.instance return ThermionFlutterPlatform.instance
.createTexture(width, height, offsetLeft, offsetRight); .createTexture(width, height, offsetLeft, offsetRight);
} }
Future destroyTexture(ThermionFlutterTexture texture) async { static Future destroyTexture(ThermionFlutterTexture texture) async {
return ThermionFlutterPlatform.instance.destroyTexture(texture); return ThermionFlutterPlatform.instance.destroyTexture(texture);
} }
@override @override
Future<ThermionFlutterTexture?> resizeTexture(ThermionFlutterTexture texture, static Future<ThermionFlutterTexture?> resizeTexture(ThermionFlutterTexture texture,
int width, int height, int offsetLeft, int offsetRight) async { int width, int height, int offsetLeft, int offsetRight) async {
return ThermionFlutterPlatform.instance return ThermionFlutterPlatform.instance
.resizeTexture(texture, width, height, offsetLeft, offsetRight); .resizeTexture(texture, width, height, offsetLeft, offsetRight);
} }
void dispose() {
ThermionFlutterPlatform.instance.dispose();
_appLifecycleListener?.dispose();
}
} }

View File

@@ -6,21 +6,24 @@ import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_in
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
/// ///
/// A subclass of [ThermionViewerFFI] that uses Flutter platform channels /// An implementation of [ThermionFlutterPlatform] that uses a Flutter platform
/// to create rendering contexts, callbacks and surfaces (either backing texture(s). /// channel to create a rendering context, resource loaders, and
/// render target(s).
/// ///
class ThermionFlutterFFI extends ThermionFlutterPlatform { class ThermionFlutterFFI extends ThermionFlutterPlatform {
final _channel = const MethodChannel("dev.thermion.flutter/event"); final _channel = const MethodChannel("dev.thermion.flutter/event");
late final ThermionViewerFFI viewer; ThermionViewerFFI? _viewer;
ThermionFlutterFFI._() {}
static void registerWith() { static void registerWith() {
ThermionFlutterPlatform.instance = ThermionFlutterFFI(); ThermionFlutterPlatform.instance = ThermionFlutterFFI._();
} }
final _textures = <ThermionFlutterTexture>{}; final _textures = <ThermionFlutterTexture>{};
Future initialize({String? uberArchivePath}) async { Future<ThermionViewer> createViewer({String? uberArchivePath}) async {
var resourceLoader = Pointer<Void>.fromAddress( var resourceLoader = Pointer<Void>.fromAddress(
await _channel.invokeMethod("getResourceLoaderWrapper")); await _channel.invokeMethod("getResourceLoaderWrapper"));
@@ -46,22 +49,24 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform {
? nullptr ? nullptr
: Pointer<Void>.fromAddress(sharedContext); : Pointer<Void>.fromAddress(sharedContext);
viewer = ThermionViewerFFI( _viewer = ThermionViewerFFI(
resourceLoader: resourceLoader, resourceLoader: resourceLoader,
renderCallback: renderCallback, renderCallback: renderCallback,
renderCallbackOwner: renderCallbackOwner, renderCallbackOwner: renderCallbackOwner,
driver: driverPtr, driver: driverPtr,
sharedContext: sharedContextPtr, sharedContext: sharedContextPtr,
uberArchivePath: uberArchivePath); uberArchivePath: uberArchivePath);
await viewer.initialized; await _viewer!.initialized;
return _viewer!;
} }
bool _creatingTexture = false; bool _creatingTexture = false;
bool _destroyingTexture = false;
Future _waitForTextureCreationToComplete() async { Future _waitForTextureCreationToComplete() async {
var iter = 0; var iter = 0;
while (_creatingTexture) { while (_creatingTexture || _destroyingTexture) {
await Future.delayed(Duration(milliseconds: 50)); await Future.delayed(Duration(milliseconds: 50));
iter++; iter++;
if (iter > 10) { if (iter > 10) {
@@ -125,39 +130,43 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform {
print( print(
"Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress"); "Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress");
viewer.viewportDimensions = (width.toDouble(), height.toDouble()); _viewer?.viewportDimensions = (width.toDouble(), height.toDouble());
final texture = ThermionFlutterTexture( final texture = ThermionFlutterTexture(
flutterTextureId, hardwareTextureId, width, height, surfaceAddress); flutterTextureId, hardwareTextureId, width, height, surfaceAddress);
await viewer.createSwapChain(width.toDouble(), height.toDouble(), await _viewer?.createSwapChain(width.toDouble(), height.toDouble(),
surface: texture.surfaceAddress == null surface: texture.surfaceAddress == null
? nullptr ? nullptr
: Pointer<Void>.fromAddress(texture.surfaceAddress!)); : Pointer<Void>.fromAddress(texture.surfaceAddress!));
if (texture.hardwareTextureId != null) { if (texture.hardwareTextureId != null) {
print("Creating render target");
// ignore: unused_local_variable // ignore: unused_local_variable
var renderTarget = await viewer.createRenderTarget( var renderTarget = await _viewer?.createRenderTarget(
width.toDouble(), height.toDouble(), texture.hardwareTextureId!); width.toDouble(), height.toDouble(), texture.hardwareTextureId!);
} }
await viewer.updateViewportAndCameraProjection( await _viewer?.updateViewportAndCameraProjection(width.toDouble(), height.toDouble());
width.toDouble(), height.toDouble()); _viewer?.render();
viewer.render();
_creatingTexture = false; _creatingTexture = false;
_textures.add(texture); _textures.add(texture);
return texture; return texture;
} }
/// ///
/// Called by [ThermionWidget] to destroy a texture. Don't call this yourself. /// Destroy a texture and clean up the texture cache (if applicable).
/// ///
Future destroyTexture(ThermionFlutterTexture texture) async { Future destroyTexture(ThermionFlutterTexture texture) async {
await _channel.invokeMethod("destroyTexture", texture.flutterTextureId); if (_creatingTexture || _destroyingTexture) {
throw Exception("Cannot destroy texture while concurrent call to createTexture/destroyTexture has not completed");
}
_destroyingTexture = true;
_textures.remove(texture); _textures.remove(texture);
await _channel.invokeMethod("destroyTexture", texture.flutterTextureId);
_destroyingTexture = false;
} }
bool _resizing = false; bool _resizing = false;
@@ -172,14 +181,14 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform {
throw Exception("Resize underway"); throw Exception("Resize underway");
} }
if ((width - viewer.viewportDimensions.$1).abs() < 0.001 || if ((width - _viewer!.viewportDimensions.$1).abs() < 0.001 ||
(height - viewer.viewportDimensions.$2).abs() < 0.001) { (height - _viewer!.viewportDimensions.$2).abs() < 0.001) {
return texture; return texture;
} }
_resizing = true; _resizing = true;
bool wasRendering = viewer.rendering; bool wasRendering = _viewer!.rendering;
await viewer.setRendering(false); await _viewer!.setRendering(false);
await viewer.destroySwapChain(); await _viewer!.destroySwapChain();
await destroyTexture(texture); await destroyTexture(texture);
var result = await _channel var result = await _channel
@@ -188,34 +197,29 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform {
if (result == null || result[0] == -1) { if (result == null || result[0] == -1) {
throw Exception("Failed to create texture"); throw Exception("Failed to create texture");
} }
viewer.viewportDimensions = (width.toDouble(), height.toDouble()); _viewer!.viewportDimensions = (width.toDouble(), height.toDouble());
var newTexture = var newTexture =
ThermionFlutterTexture(result[0], result[1], width, height, result[2]); ThermionFlutterTexture(result[0], result[1], width, height, result[2]);
await viewer.createSwapChain(width.toDouble(), height.toDouble(), await _viewer!.createSwapChain(width.toDouble(), height.toDouble(),
surface: newTexture.surfaceAddress == null surface: newTexture.surfaceAddress == null
? nullptr ? nullptr
: Pointer<Void>.fromAddress(newTexture.surfaceAddress!)); : Pointer<Void>.fromAddress(newTexture.surfaceAddress!));
if (newTexture.hardwareTextureId != null) { if (newTexture.hardwareTextureId != null) {
// ignore: unused_local_variable // ignore: unused_local_variable
var renderTarget = await viewer.createRenderTarget( var renderTarget = await _viewer!.createRenderTarget(
width.toDouble(), height.toDouble(), newTexture.hardwareTextureId!); width.toDouble(), height.toDouble(), newTexture.hardwareTextureId!);
} }
await viewer.updateViewportAndCameraProjection( await _viewer!
width.toDouble(), height.toDouble()); .updateViewportAndCameraProjection(width.toDouble(), height.toDouble());
viewer.viewportDimensions = (width.toDouble(), height.toDouble()); _viewer!.viewportDimensions = (width.toDouble(), height.toDouble());
if (wasRendering) { if (wasRendering) {
await viewer.setRendering(true); await _viewer!.setRendering(true);
} }
_textures.add(newTexture); _textures.add(newTexture);
_resizing = false; _resizing = false;
return newTexture; return newTexture;
} }
@override
void dispose() {
// TODO: implement dispose
}
} }