diff --git a/.lfsconfig b/.lfsconfig new file mode 100644 index 00000000..2cf1bce1 --- /dev/null +++ b/.lfsconfig @@ -0,0 +1,2 @@ +[lfs] + fetchexclude = * diff --git a/lib/src/widgets/inherited_widgets.dart b/lib/src/widgets/inherited_widgets.dart index 58f982fb..8b54d6c5 100644 --- a/lib/src/widgets/inherited_widgets.dart +++ b/lib/src/widgets/inherited_widgets.dart @@ -19,6 +19,19 @@ class SharedRenderTexture { final List painters = []; final GlobalKey panelKey; + bool _dirty = true; + bool _scheduled = false; + + /// When true, [_paintShared] skips the clear→paint→flush cycle if no + /// painter called [markDirty] since the last flush. When false (default), + /// every scheduled paint runs the full cycle — identical to upstream. + bool dirtyTrackingEnabled = false; + + /// Called every frame by the render object's ticker with the frame's + /// elapsed seconds. Listeners can accumulate time and call [markDirty] + /// when a state-machine advance is needed. + void Function(double elapsedSeconds)? onFrameTick; + SharedRenderTexture({ required this.texture, required this.devicePixelRatio, @@ -26,19 +39,29 @@ class SharedRenderTexture { required this.panelKey, }); + /// Mark the texture as needing a repaint on the next scheduled frame. + void markDirty() { + _dirty = true; + } + /// Paint the shared render texture. + /// + /// When [dirtyTrackingEnabled] is true and the texture is clean, the entire + /// clear→paint→flush cycle is skipped. The render-object ticker stays alive + /// independently and invokes [onFrameTick] each frame so external code can + /// call [markDirty] when a state-machine advance is needed. void _paintShared(_) { + _scheduled = false; + if (dirtyTrackingEnabled && !_dirty) return; + texture.clear(backgroundColor); for (final painter in painters) { painter.paintIntoSharedTexture(texture); } texture.flush(devicePixelRatio); - - _scheduled = false; + _dirty = false; } - bool _scheduled = false; - /// Schedule a paint of the shared render texture. void schedulePaint() { if (_scheduled) { @@ -52,11 +75,13 @@ class SharedRenderTexture { void addPainter(SharedTexturePainter painter) { painters.add(painter); painters.sort((a, b) => a.sharedDrawOrder.compareTo(b.sharedDrawOrder)); + markDirty(); } /// Remove a painter from the shared render texture. void removePainter(SharedTexturePainter painter) { painters.remove(painter); + markDirty(); } } diff --git a/lib/src/widgets/shared_texture_view.dart b/lib/src/widgets/shared_texture_view.dart index 964e75ab..f6f1902b 100644 --- a/lib/src/widgets/shared_texture_view.dart +++ b/lib/src/widgets/shared_texture_view.dart @@ -107,6 +107,10 @@ class SharedTextureViewRenderObject extends RiveNativeRenderBox int drawOrder = 1; + /// Accumulated elapsed seconds across frames while dirty tracking skips + /// the paint cycle. Reset to 0 after each [paintIntoSharedTexture] call. + double _accumulatedElapsed = 0; + SharedRenderTexture get shared => _shared; set shared(SharedRenderTexture value) { if (_shared == value) { @@ -182,6 +186,12 @@ class SharedTextureViewRenderObject extends RiveNativeRenderBox Offset panelPosition = renderBox.localToGlobal(Offset.zero); Offset globalPosition = localToGlobal(Offset.zero) - panelPosition; + // When dirty tracking is enabled, use accumulated elapsed time so the + // controller receives the full wall-clock delta since the last advance. + final effectiveElapsed = + _shared.dirtyTrackingEnabled ? _accumulatedElapsed : elapsedSeconds; + _accumulatedElapsed = 0; + final renderer = texture.renderer; renderer.save(); @@ -195,7 +205,7 @@ class SharedTextureViewRenderObject extends RiveNativeRenderBox texture, devicePixelRatio, size, - elapsedSeconds, + effectiveElapsed, ) ?? false; if (_shouldAdvance) { @@ -220,6 +230,8 @@ class SharedTextureViewRenderObject extends RiveNativeRenderBox @override void frameCallback(Duration duration) { super.frameCallback(duration); + _accumulatedElapsed += elapsedSeconds; + _shared.onFrameTick?.call(elapsedSeconds); _shared.schedulePaint(); }