make ThermionFlutterPlugin static, remove dispose() and add internal listener for ThermionViewer.onDispose
This commit is contained in:
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user