From fe57e817f3bcbcb860e2a9ca751ce0b4c99fce04 Mon Sep 17 00:00:00 2001 From: mjtalbot <hello@rive.app> Date: Tue, 28 Feb 2023 11:53:47 +0000 Subject: [PATCH] Fix negative speeds in state machines look commit by commit potentially, last couple of commits are running core generator & its quite a small change code wise, so it'd be good to get feedback on the actual change sooner rather than later there are at least a couple of changes in there that should make you question if this is a good idea so i expect some feedback. add to cpp: (i just got it working in the viewer, probably broke some things) - [x] negative time fix - [x] combine state speed to determine time for state - [x] no longer carry spilled time into new advances add test - [x] negative time fix to state machines - [x] negative combined time state fix to state machines - [ ] no longer carry spilled time into new advances @luigi-rosso i tried adding tests for this, but i was not really able to construct a state machine instance (with an animations, an animation state and the right transition) in tests I had to add a bunch of public methods all over the place, can you show me the way? probably not a blocker for merging this) apply changes to rive_flutter - [x] run script (ran both core generator and core generator runtime, the runtime one wanted to remove a load of comments so i didnt let those bits be committed. but still.. annoying) Diffs= bc6c6f467 Fix negative speeds in state machines (#4887) 040a27c1c Generate Android builds directly from premake (#4871) 12285f625 Put SIMD perf warnings behind a flag (#4861) 157a399d2 update id in code (#4855) --- .rive_head | 2 +- .../animation/advanceable_state_base.dart | 46 +++++++++++++++++++ .../animation/animation_state_base.dart | 6 ++- lib/src/generated/rive_core_context.dart | 14 ++++++ .../animation/advanceable_state.dart | 7 +++ .../animation/animation_state_instance.dart | 13 ++++-- .../rive_core/animation/linear_animation.dart | 17 ++++--- .../animation/linear_animation_instance.dart | 34 +++++++++----- .../rive_core/animation/state_instance.dart | 5 +- lib/src/rive_core/artboard.dart | 5 ++ lib/src/rive_core/shapes/image.dart | 1 - .../rive_core/state_machine_controller.dart | 13 +++++- 12 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 lib/src/generated/animation/advanceable_state_base.dart create mode 100644 lib/src/rive_core/animation/advanceable_state.dart diff --git a/.rive_head b/.rive_head index 7a3ff79..3727ac5 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -ffeb9afafb69c3bf34e2df8c4c0c205083f2aabf +bc6c6f467b8f1e0f794be093a514768bd068088a diff --git a/lib/src/generated/animation/advanceable_state_base.dart b/lib/src/generated/animation/advanceable_state_base.dart new file mode 100644 index 0000000..736535d --- /dev/null +++ b/lib/src/generated/animation/advanceable_state_base.dart @@ -0,0 +1,46 @@ +/// Core automatically generated +/// lib/src/generated/animation/advanceable_state_base.dart. +/// Do not modify manually. + +import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart'; +import 'package:rive/src/rive_core/animation/layer_state.dart'; + +abstract class AdvanceableStateBase extends LayerState { + static const int typeKey = 145; + @override + int get coreType => AdvanceableStateBase.typeKey; + @override + Set<int> get coreTypes => { + AdvanceableStateBase.typeKey, + LayerStateBase.typeKey, + StateMachineLayerComponentBase.typeKey + }; + + /// -------------------------------------------------------------------------- + /// Speed field with key 292. + static const double speedInitialValue = 1; + double _speed = speedInitialValue; + static const int speedPropertyKey = 292; + double get speed => _speed; + + /// Change the [_speed] field value. + /// [speedChanged] will be invoked only if the field's value has changed. + set speed(double value) { + if (_speed == value) { + return; + } + double from = _speed; + _speed = value; + if (hasValidated) { + speedChanged(from, value); + } + } + + void speedChanged(double from, double to); + + @override + void copy(covariant AdvanceableStateBase source) { + super.copy(source); + _speed = source._speed; + } +} diff --git a/lib/src/generated/animation/animation_state_base.dart b/lib/src/generated/animation/animation_state_base.dart index b571096..4e83f92 100644 --- a/lib/src/generated/animation/animation_state_base.dart +++ b/lib/src/generated/animation/animation_state_base.dart @@ -2,16 +2,18 @@ /// lib/src/generated/animation/animation_state_base.dart. /// Do not modify manually. +import 'package:rive/src/generated/animation/layer_state_base.dart'; import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart'; -import 'package:rive/src/rive_core/animation/layer_state.dart'; +import 'package:rive/src/rive_core/animation/advanceable_state.dart'; -abstract class AnimationStateBase extends LayerState { +abstract class AnimationStateBase extends AdvanceableState { static const int typeKey = 61; @override int get coreType => AnimationStateBase.typeKey; @override Set<int> get coreTypes => { AnimationStateBase.typeKey, + AdvanceableStateBase.typeKey, LayerStateBase.typeKey, StateMachineLayerComponentBase.typeKey }; diff --git a/lib/src/generated/rive_core_context.dart b/lib/src/generated/rive_core_context.dart index ab14603..b304fd4 100644 --- a/lib/src/generated/rive_core_context.dart +++ b/lib/src/generated/rive_core_context.dart @@ -6,6 +6,7 @@ import 'package:rive/src/core/field_types/core_double_type.dart'; import 'package:rive/src/core/field_types/core_field_type.dart'; import 'package:rive/src/core/field_types/core_string_type.dart'; import 'package:rive/src/core/field_types/core_uint_type.dart'; +import 'package:rive/src/generated/animation/advanceable_state_base.dart'; import 'package:rive/src/generated/animation/blend_animation_base.dart'; import 'package:rive/src/generated/animation/cubic_ease_interpolator_base.dart'; import 'package:rive/src/generated/animation/cubic_interpolator_base.dart'; @@ -567,6 +568,11 @@ class RiveCoreContext { object.inputId = value; } break; + case AdvanceableStateBase.speedPropertyKey: + if (object is AdvanceableStateBase && value is double) { + object.speed = value; + } + break; case AnimationStateBase.animationIdPropertyKey: if (object is AnimationStateBase && value is int) { object.animationId = value; @@ -1366,6 +1372,7 @@ class RiveCoreContext { case LinearAnimationBase.speedPropertyKey: case NestedLinearAnimationBase.mixPropertyKey: case NestedSimpleAnimationBase.speedPropertyKey: + case AdvanceableStateBase.speedPropertyKey: case StateMachineNumberBase.valuePropertyKey: case CubicInterpolatorBase.x1PropertyKey: case CubicInterpolatorBase.y1PropertyKey: @@ -1668,6 +1675,8 @@ class RiveCoreContext { return (object as NestedLinearAnimationBase).mix; case NestedSimpleAnimationBase.speedPropertyKey: return (object as NestedSimpleAnimationBase).speed; + case AdvanceableStateBase.speedPropertyKey: + return (object as AdvanceableStateBase).speed; case StateMachineNumberBase.valuePropertyKey: return (object as StateMachineNumberBase).value; case CubicInterpolatorBase.x1PropertyKey: @@ -2351,6 +2360,11 @@ class RiveCoreContext { object.speed = value; } break; + case AdvanceableStateBase.speedPropertyKey: + if (object is AdvanceableStateBase) { + object.speed = value; + } + break; case StateMachineNumberBase.valuePropertyKey: if (object is StateMachineNumberBase) { object.value = value; diff --git a/lib/src/rive_core/animation/advanceable_state.dart b/lib/src/rive_core/animation/advanceable_state.dart new file mode 100644 index 0000000..4936773 --- /dev/null +++ b/lib/src/rive_core/animation/advanceable_state.dart @@ -0,0 +1,7 @@ +import 'package:rive/src/generated/animation/advanceable_state_base.dart'; +export 'package:rive/src/generated/animation/advanceable_state_base.dart'; + +abstract class AdvanceableState extends AdvanceableStateBase { + @override + void speedChanged(double from, double to) {} +} diff --git a/lib/src/rive_core/animation/animation_state_instance.dart b/lib/src/rive_core/animation/animation_state_instance.dart index 906fe0b..2de118b 100644 --- a/lib/src/rive_core/animation/animation_state_instance.dart +++ b/lib/src/rive_core/animation/animation_state_instance.dart @@ -5,16 +5,20 @@ import 'package:rive/src/rive_core/animation/state_instance.dart'; /// Simple wrapper around [LinearAnimationInstance] making it compatible with /// the [StateMachine]'s [StateInstance] interface. -class AnimationStateInstance extends StateInstance { +class AnimationStateInstance extends StateInstance<AnimationState> { final LinearAnimationInstance animationInstance; AnimationStateInstance(AnimationState state) : assert(state.animation != null), - animationInstance = LinearAnimationInstance(state.animation!), + animationInstance = LinearAnimationInstance( + state.animation!, + speedMultiplier: state.speed, + ), super(state); @override - void advance(double seconds, _) => animationInstance.advance(seconds); + void advance(double seconds, _) => + animationInstance.advance(seconds * state.speed); @override void apply(CoreContext core, double mix) => animationInstance.animation @@ -22,4 +26,7 @@ class AnimationStateInstance extends StateInstance { @override bool get keepGoing => animationInstance.keepGoing; + + @override + void clearSpilledTime() => animationInstance.clearSpilledTime(); } diff --git a/lib/src/rive_core/animation/linear_animation.dart b/lib/src/rive_core/animation/linear_animation.dart index 772b9fa..5f40f4c 100644 --- a/lib/src/rive_core/animation/linear_animation.dart +++ b/lib/src/rive_core/animation/linear_animation.dart @@ -38,11 +38,22 @@ class LinearAnimation extends LinearAnimationBase { return true; } + /// Returns the seconds where the animiation work area starts double get startSeconds => (enableWorkArea ? workStart : 0).toDouble() / fps; + + /// Returns the seconds where the animiation work area ends double get endSeconds => (enableWorkArea ? workEnd : duration).toDouble() / fps; + + /// Returns the length of the animation double get durationSeconds => endSeconds - startSeconds; + /// Returns the end time of the animation in seconds, considering speed + double get endTime => (speed >= 0) ? endSeconds : startSeconds; + + /// Returns the start time of the animation in seconds, considering speed + double get startTime => (speed >= 0) ? startSeconds : endSeconds; + /// Pass in a different [core] context if you want to apply the animation to a /// different instance. This isn't meant to be used yet but left as mostly a /// note to remember that at runtime we have to support applying animations to @@ -79,12 +90,6 @@ class LinearAnimation extends LinearAnimationBase { @override void workStartChanged(int from, int to) {} - /// Returns the end time of the animation in seconds - double get endTime => (enableWorkArea ? workEnd : duration).toDouble() / fps; - - /// Returns the start time of the animation in seconds - double get startTime => (enableWorkArea ? workStart : 0).toDouble() / fps; - /// Convert a global clock to local seconds (takes into consideration work /// area start/end, speed, looping). double globalToLocalTime(double seconds) { diff --git a/lib/src/rive_core/animation/linear_animation_instance.dart b/lib/src/rive_core/animation/linear_animation_instance.dart index 31a4db2..dc28497 100644 --- a/lib/src/rive_core/animation/linear_animation_instance.dart +++ b/lib/src/rive_core/animation/linear_animation_instance.dart @@ -8,7 +8,7 @@ class LinearAnimationInstance { double _time = 0; double _totalTime = 0; double _lastTotalTime = 0; - int _direction = 1; + double _direction = 1; bool _didLoop = false; bool get didLoop => _didLoop; double _spilledTime = 0; @@ -17,21 +17,23 @@ class LinearAnimationInstance { double get totalTime => _totalTime; double get lastTotalTime => _lastTotalTime; - LinearAnimationInstance(this.animation) + LinearAnimationInstance(this.animation, {double speedMultiplier = 1.0}) : _time = - (animation.enableWorkArea ? animation.workStart : 0).toDouble() / - animation.fps; + (speedMultiplier >= 0) ? animation.startTime : animation.endTime; - /// Note that when time is set, the direction will be changed to 1 + /// NOTE: that when time is set, the direction will be changed to 1 set time(double value) { if (_time == value) { return; } + // Make sure to keep last and total in relative lockstep so state machines // can track change even when setting time. var diff = _totalTime - _lastTotalTime; _time = _totalTime = value; _lastTotalTime = _totalTime - diff; + + // NOTE: will cause ping-pongs to get reset if "seeking" _direction = 1; } @@ -39,16 +41,18 @@ class LinearAnimationInstance { double get time => _time; /// Direction should only be +1 or -1 - set direction(int value) => _direction = value == -1 ? -1 : 1; + set direction(double value) => _direction = value == -1 ? -1 : 1; /// Returns the animation's play direction: 1 for forwards, -1 for backwards - int get direction => _direction; + double get direction => _direction; double get progress => - (_time - animation.startTime) / (animation.endTime - animation.startTime); + (_time - animation.startTime).abs() / + (animation.endTime - animation.startTime).abs(); /// Resets the animation to the starting frame - void reset() => _time = animation.startTime; + void reset({double speedMultiplier = 1}) => + _time = (speedMultiplier >= 0) ? animation.startTime : animation.endTime; /// Whether the controller driving this animation should keep requesting /// frames be drawn. @@ -60,14 +64,20 @@ class LinearAnimationInstance { animation.apply(time, coreContext: core, mix: mix); } + void clearSpilledTime() { + _spilledTime = 0; + } + bool advance(double elapsedSeconds) { var deltaSeconds = elapsedSeconds * animation.speed * _direction; if (deltaSeconds == 0) { // we say keep going, if you advance by 0. - // could argue that any further advances by 0 result in nothing so you should not keep going - // could argue its saying, we are not at the end of the animation yet, so keep going - // our runtimes currently expect the latter, so we say keep going! + // could argue that any further advances by 0 result in nothing so you + // should not keep going + // could argue its saying, we are not at the end of the animation yet, + // so keep going our runtimes currently expect the latter, so we say keep + // going! _didLoop = false; return true; } diff --git a/lib/src/rive_core/animation/state_instance.dart b/lib/src/rive_core/animation/state_instance.dart index 7fb032d..b48bdf2 100644 --- a/lib/src/rive_core/animation/state_instance.dart +++ b/lib/src/rive_core/animation/state_instance.dart @@ -6,8 +6,8 @@ import 'package:rive/src/rive_core/state_machine_controller.dart'; /// [LayerController] of a [StateMachineController]. Abstract representation of /// an Animation (for [AnimationState]) or set of Animations in the case of a /// [BlendState]. -abstract class StateInstance { - final LayerState state; +abstract class StateInstance<T extends LayerState> { + final T state; StateInstance(this.state); @@ -17,6 +17,7 @@ abstract class StateInstance { bool get keepGoing; void dispose() {} + void clearSpilledTime() {} } /// A single one of these is created per Layer which just represents/wraps the diff --git a/lib/src/rive_core/artboard.dart b/lib/src/rive_core/artboard.dart index 9b730b3..ecc9c9e 100644 --- a/lib/src/rive_core/artboard.dart +++ b/lib/src/rive_core/artboard.dart @@ -578,4 +578,9 @@ class Artboard extends ArtboardBase with ShapePaintContainer { void defaultStateMachineIdChanged(int from, int to) { defaultStateMachine = to == Core.missingId ? null : context.resolve(to); } + + @override + void selectedAnimationsChanged(List<int> from, List<int> to) { + // If the selection is different, we might have a different Editing Animation + } } diff --git a/lib/src/rive_core/shapes/image.dart b/lib/src/rive_core/shapes/image.dart index 3eabebb..212406a 100644 --- a/lib/src/rive_core/shapes/image.dart +++ b/lib/src/rive_core/shapes/image.dart @@ -1,6 +1,5 @@ import 'dart:ui' as ui; -import 'package:rive/src/core/core.dart'; import 'package:rive/src/generated/shapes/image_base.dart'; import 'package:rive/src/rive_core/assets/file_asset.dart'; import 'package:rive/src/rive_core/assets/image_asset.dart'; diff --git a/lib/src/rive_core/state_machine_controller.dart b/lib/src/rive_core/state_machine_controller.dart index 8abd9b3..f870799 100644 --- a/lib/src/rive_core/state_machine_controller.dart +++ b/lib/src/rive_core/state_machine_controller.dart @@ -109,7 +109,11 @@ class LayerController { bool apply(CoreContext core, double elapsedSeconds) { if (_currentState != null) { - _currentState!.advance(elapsedSeconds, controller); + if (_currentState?.keepGoing == true) { + // NOTE: if we advance even after we have been told not to keep going + // we are not just inappropriate we are also re adding spilled time + _currentState!.advance(elapsedSeconds, controller); + } } _updateMix(elapsedSeconds); @@ -136,6 +140,13 @@ class LayerController { _apply(core); + // give the current state the oportunity to clear spilled time, so that we + // do not carry this over into another iteration. + _currentState?.clearSpilledTime(); + + // We still need to mix in the current state if mix value is less than one + // as it still contributes to the end result. + // It may not need to advance but it does need to apply. return _mix != 1 || _waitingForExit || (_currentState?.keepGoing ?? false); }