From 5765007ecdbd3c6ec8b9dac281076bd90e40f313 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 20 May 2021 01:03:41 +0200 Subject: [PATCH 1/9] Avoid closures and extra allocations caused by transition observables. --- .../AnimatorDrivenTransition.cs | 15 +++ .../AnimatorTransitionObservable.cs | 24 +++++ .../{Transition`1.cs => Transition.cs} | 6 +- src/Avalonia.Animation/TransitionInstance.cs | 23 +++-- .../TransitionObservableBase.cs | 45 +++++++++ .../Transitions/DoubleTransition.cs | 7 +- .../TransformOperationsTransition.cs | 18 ++-- .../Animations/TransitionBenchmark.cs | 92 +++++++++++++++++++ 8 files changed, 208 insertions(+), 22 deletions(-) create mode 100644 src/Avalonia.Animation/AnimatorDrivenTransition.cs create mode 100644 src/Avalonia.Animation/AnimatorTransitionObservable.cs rename src/Avalonia.Animation/{Transition`1.cs => Transition.cs} (96%) create mode 100644 src/Avalonia.Animation/TransitionObservableBase.cs create mode 100644 tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs diff --git a/src/Avalonia.Animation/AnimatorDrivenTransition.cs b/src/Avalonia.Animation/AnimatorDrivenTransition.cs new file mode 100644 index 0000000000..eb4d6f978d --- /dev/null +++ b/src/Avalonia.Animation/AnimatorDrivenTransition.cs @@ -0,0 +1,15 @@ +using System; +using Avalonia.Animation.Animators; + +namespace Avalonia.Animation +{ + public class AnimatorDrivenTransition : Transition where TAnimator : Animator, new() + { + private static readonly TAnimator s_animator = new TAnimator(); + + public override IObservable DoTransition(IObservable progress, T oldValue, T newValue) + { + return new AnimatorTransitionObservable(s_animator, progress, oldValue, newValue); + } + } +} diff --git a/src/Avalonia.Animation/AnimatorTransitionObservable.cs b/src/Avalonia.Animation/AnimatorTransitionObservable.cs new file mode 100644 index 0000000000..4f888e1f15 --- /dev/null +++ b/src/Avalonia.Animation/AnimatorTransitionObservable.cs @@ -0,0 +1,24 @@ +using System; +using Avalonia.Animation.Animators; + +namespace Avalonia.Animation +{ + public class AnimatorTransitionObservable : TransitionObservableBase where TAnimator : Animator + { + private readonly TAnimator _animator; + private readonly T _oldValue; + private readonly T _newValue; + + public AnimatorTransitionObservable(TAnimator animator, IObservable progress, T oldValue, T newValue) : base(progress) + { + _animator = animator; + _oldValue = oldValue; + _newValue = newValue; + } + + protected override T ProduceValue(double progress) + { + return _animator.Interpolate(progress, _oldValue, _newValue); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition.cs similarity index 96% rename from src/Avalonia.Animation/Transition`1.cs rename to src/Avalonia.Animation/Transition.cs index 4542a137e5..4115c95c0f 100644 --- a/src/Avalonia.Animation/Transition`1.cs +++ b/src/Avalonia.Animation/Transition.cs @@ -1,7 +1,5 @@ -using System; -using System.Reactive.Linq; +using System; using Avalonia.Animation.Easings; -using Avalonia.Animation.Utils; namespace Avalonia.Animation { @@ -56,4 +54,4 @@ namespace Avalonia.Animation return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } } -} +} \ No newline at end of file diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index 5184341324..58dd71e8a5 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -1,8 +1,4 @@ -using Avalonia.Metadata; using System; -using System.Reactive.Linq; -using Avalonia.Animation.Easings; -using Avalonia.Animation.Utils; using Avalonia.Reactive; using Avalonia.Utilities; @@ -11,7 +7,7 @@ namespace Avalonia.Animation /// /// Handles the timing and lifetime of a . /// - internal class TransitionInstance : SingleSubscriberObservableBase + internal class TransitionInstance : SingleSubscriberObservableBase, IObserver { private IDisposable _timerSubscription; private TimeSpan _delay; @@ -76,8 +72,23 @@ namespace Avalonia.Animation protected override void Subscribed() { _clock = new Clock(_baseClock); - _timerSubscription = _clock.Subscribe(TimerTick); + _timerSubscription = _clock.Subscribe(this); PublishNext(0.0d); } + + void IObserver.OnCompleted() + { + PublishCompleted(); + } + + void IObserver.OnError(Exception error) + { + PublishError(error); + } + + void IObserver.OnNext(TimeSpan value) + { + TimerTick(value); + } } } diff --git a/src/Avalonia.Animation/TransitionObservableBase.cs b/src/Avalonia.Animation/TransitionObservableBase.cs new file mode 100644 index 0000000000..e18b6859f5 --- /dev/null +++ b/src/Avalonia.Animation/TransitionObservableBase.cs @@ -0,0 +1,45 @@ +using System; +using Avalonia.Reactive; + +#nullable enable + +namespace Avalonia.Animation +{ + public abstract class TransitionObservableBase : SingleSubscriberObservableBase, IObserver + { + private readonly IObservable _progress; + private IDisposable? _progressSubscription; + + protected TransitionObservableBase(IObservable progress) + { + _progress = progress; + } + + protected override void Unsubscribed() + { + _progressSubscription?.Dispose(); + } + + protected override void Subscribed() + { + _progressSubscription = _progress.Subscribe(this); + } + + protected abstract T ProduceValue(double progress); + + void IObserver.OnCompleted() + { + PublishCompleted(); + } + + void IObserver.OnError(Exception error) + { + PublishError(error); + } + + void IObserver.OnNext(double value) + { + PublishNext(ProduceValue(value)); + } + } +} diff --git a/src/Avalonia.Animation/Transitions/DoubleTransition.cs b/src/Avalonia.Animation/Transitions/DoubleTransition.cs index d5bb1aac20..28f630eb98 100644 --- a/src/Avalonia.Animation/Transitions/DoubleTransition.cs +++ b/src/Avalonia.Animation/Transitions/DoubleTransition.cs @@ -1,6 +1,5 @@ using System; using System.Reactive.Linq; - using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -8,7 +7,11 @@ namespace Avalonia.Animation /// /// Transition class that handles with types. /// - public class DoubleTransition : Transition + public class DoubleTransition : AnimatorDrivenTransition + { + } + + public class DoubleTransitionOld : Transition { private static readonly DoubleAnimator s_animator = new DoubleAnimator(); diff --git a/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs index 104acb71ad..ca1528a561 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs @@ -1,28 +1,26 @@ using System; -using System.Reactive.Linq; using Avalonia.Animation.Animators; using Avalonia.Media; +using Avalonia.Media.Transformation; + +#nullable enable namespace Avalonia.Animation { public class TransformOperationsTransition : Transition { - private static readonly TransformOperationsAnimator _operationsAnimator = new TransformOperationsAnimator(); + private static readonly TransformOperationsAnimator s_operationsAnimator = new TransformOperationsAnimator(); - public override IObservable DoTransition(IObservable progress, + public override IObservable DoTransition( + IObservable progress, ITransform oldValue, ITransform newValue) { var oldTransform = TransformOperationsAnimator.EnsureOperations(oldValue); var newTransform = TransformOperationsAnimator.EnsureOperations(newValue); - return progress - .Select(p => - { - var f = Easing.Ease(p); - - return _operationsAnimator.Interpolate(f, oldTransform, newTransform); - }); + return new AnimatorTransitionObservable( + s_operationsAnimator, progress, oldTransform, newTransform); } } } diff --git a/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs new file mode 100644 index 0000000000..16aad0c21b --- /dev/null +++ b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reactive.Subjects; +using System.Runtime.CompilerServices; +using Avalonia.Animation; +using Avalonia.Layout; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Animations +{ + [MemoryDiagnoser] + public class TransitionBenchmark + { + private readonly DoubleTransition _transition; + private readonly DoubleTransitionOld _oldTransition; + private readonly int _frameCount; + private readonly Subject _timeProducer; + private readonly List _producedValues; + + public TransitionBenchmark() + { + _frameCount = 100; + + _oldTransition = new DoubleTransitionOld + { + Duration = TimeSpan.FromMilliseconds(_frameCount), Property = Layoutable.WidthProperty + }; + + _transition = new DoubleTransition + { + Duration = TimeSpan.FromMilliseconds(_frameCount), Property = Layoutable.WidthProperty + }; + + _timeProducer = new Subject(); + _producedValues = new List(_frameCount); + } + + [Benchmark(Baseline = true)] + [MethodImpl(MethodImplOptions.NoInlining)] + public void OldTransition() + { + TransitionCommon(_oldTransition); + } + + [Benchmark] + [MethodImpl(MethodImplOptions.NoInlining)] + public void NewTransition() + { + TransitionCommon(_transition); + } + + private void TransitionCommon(Transition transition) + { + var transitionObs = transition.DoTransition(_timeProducer, 0, 1); + + _producedValues.Clear(); + + using var transitionSub = transitionObs.Subscribe(new AddValueObserver(_producedValues)); + + for (int i = 0; i < _frameCount; i++) + { + _timeProducer.OnNext(TimeSpan.FromMilliseconds(i).TotalSeconds); + } + + Debug.Assert(_producedValues.Count == _frameCount); + } + + private class AddValueObserver : IObserver + { + private readonly List _values; + + public AddValueObserver(List values) + { + _values = values; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(double value) + { + _values.Add(value); + } + } + } +} From 9bf33631ab267e6cd8a42439bc4a0cdec6655fa6 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 20 May 2021 01:31:27 +0200 Subject: [PATCH 2/9] Make base transition abstract. --- src/Avalonia.Animation/AnimatorDrivenTransition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/AnimatorDrivenTransition.cs b/src/Avalonia.Animation/AnimatorDrivenTransition.cs index eb4d6f978d..13139f8e8d 100644 --- a/src/Avalonia.Animation/AnimatorDrivenTransition.cs +++ b/src/Avalonia.Animation/AnimatorDrivenTransition.cs @@ -3,7 +3,7 @@ using Avalonia.Animation.Animators; namespace Avalonia.Animation { - public class AnimatorDrivenTransition : Transition where TAnimator : Animator, new() + public abstract class AnimatorDrivenTransition : Transition where TAnimator : Animator, new() { private static readonly TAnimator s_animator = new TAnimator(); From 82ca716cca8cd879becc13bd5351d296ef67693e Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 20 May 2021 01:32:23 +0200 Subject: [PATCH 3/9] Remove ref counting from ClockBase. --- src/Avalonia.Animation/ClockBase.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Animation/ClockBase.cs b/src/Avalonia.Animation/ClockBase.cs index a2b29e728e..9fa6a85a6c 100644 --- a/src/Avalonia.Animation/ClockBase.cs +++ b/src/Avalonia.Animation/ClockBase.cs @@ -8,9 +8,7 @@ namespace Avalonia.Animation { public class ClockBase : IClock { - private ClockObservable _observable; - - private IObservable _connectedObservable; + private readonly ClockObservable _observable; private TimeSpan? _previousTime; private TimeSpan _internalTime; @@ -18,7 +16,6 @@ namespace Avalonia.Animation protected ClockBase() { _observable = new ClockObservable(); - _connectedObservable = _observable.Publish().RefCount(); } protected bool HasSubscriptions => _observable.HasSubscriptions; @@ -58,7 +55,7 @@ namespace Avalonia.Animation public IDisposable Subscribe(IObserver observer) { - return _connectedObservable.Subscribe(observer); + return _observable.Subscribe(observer); } private class ClockObservable : LightweightObservableBase From 54090bc1f7263da7c4674616be1c342fa8e8aac7 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 20 May 2021 01:47:37 +0200 Subject: [PATCH 4/9] Apply easing. --- src/Avalonia.Animation/AnimatorDrivenTransition.cs | 2 +- src/Avalonia.Animation/AnimatorTransitionObservable.cs | 9 +++++++-- .../Transitions/TransformOperationsTransition.cs | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Animation/AnimatorDrivenTransition.cs b/src/Avalonia.Animation/AnimatorDrivenTransition.cs index 13139f8e8d..3f6ac47556 100644 --- a/src/Avalonia.Animation/AnimatorDrivenTransition.cs +++ b/src/Avalonia.Animation/AnimatorDrivenTransition.cs @@ -9,7 +9,7 @@ namespace Avalonia.Animation public override IObservable DoTransition(IObservable progress, T oldValue, T newValue) { - return new AnimatorTransitionObservable(s_animator, progress, oldValue, newValue); + return new AnimatorTransitionObservable(s_animator, progress, Easing, oldValue, newValue); } } } diff --git a/src/Avalonia.Animation/AnimatorTransitionObservable.cs b/src/Avalonia.Animation/AnimatorTransitionObservable.cs index 4f888e1f15..10c19bb62b 100644 --- a/src/Avalonia.Animation/AnimatorTransitionObservable.cs +++ b/src/Avalonia.Animation/AnimatorTransitionObservable.cs @@ -1,24 +1,29 @@ using System; using Avalonia.Animation.Animators; +using Avalonia.Animation.Easings; namespace Avalonia.Animation { public class AnimatorTransitionObservable : TransitionObservableBase where TAnimator : Animator { private readonly TAnimator _animator; + private readonly Easing _easing; private readonly T _oldValue; private readonly T _newValue; - public AnimatorTransitionObservable(TAnimator animator, IObservable progress, T oldValue, T newValue) : base(progress) + public AnimatorTransitionObservable(TAnimator animator, IObservable progress, Easing easing, T oldValue, T newValue) : base(progress) { _animator = animator; + _easing = easing; _oldValue = oldValue; _newValue = newValue; } protected override T ProduceValue(double progress) { + progress = _easing.Ease(progress); + return _animator.Interpolate(progress, _oldValue, _newValue); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs index ca1528a561..73fc23ad99 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs @@ -20,7 +20,7 @@ namespace Avalonia.Animation var newTransform = TransformOperationsAnimator.EnsureOperations(newValue); return new AnimatorTransitionObservable( - s_operationsAnimator, progress, oldTransform, newTransform); + s_operationsAnimator, progress, Easing, oldTransform, newTransform); } } } From 60ad7eae12fb366475fab8eb615432af3ea05093 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 20 May 2021 20:35:07 +0200 Subject: [PATCH 5/9] More cleanup. --- src/Avalonia.Animation/Clock.cs | 5 ++-- .../TransitionObservableBase.cs | 24 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index bea6c75982..6c7ae8349a 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -10,10 +10,9 @@ namespace Avalonia.Animation { public static IClock GlobalClock => AvaloniaLocator.Current.GetService(); - private IDisposable _parentSubscription; + private readonly IDisposable _parentSubscription; - public Clock() - :this(GlobalClock) + public Clock() : this(GlobalClock) { } diff --git a/src/Avalonia.Animation/TransitionObservableBase.cs b/src/Avalonia.Animation/TransitionObservableBase.cs index e18b6859f5..9f4fb8093c 100644 --- a/src/Avalonia.Animation/TransitionObservableBase.cs +++ b/src/Avalonia.Animation/TransitionObservableBase.cs @@ -15,18 +15,6 @@ namespace Avalonia.Animation _progress = progress; } - protected override void Unsubscribed() - { - _progressSubscription?.Dispose(); - } - - protected override void Subscribed() - { - _progressSubscription = _progress.Subscribe(this); - } - - protected abstract T ProduceValue(double progress); - void IObserver.OnCompleted() { PublishCompleted(); @@ -41,5 +29,17 @@ namespace Avalonia.Animation { PublishNext(ProduceValue(value)); } + + protected override void Unsubscribed() + { + _progressSubscription?.Dispose(); + } + + protected override void Subscribed() + { + _progressSubscription = _progress.Subscribe(this); + } + + protected abstract T ProduceValue(double progress); } } From 21b7b88b0f53afb715f76adcb1332a9a6863e874 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Mon, 24 May 2021 01:52:19 +0200 Subject: [PATCH 6/9] Cleanup and refactor leftover transition types. --- .../AnimatorDrivenTransition.cs | 5 +++ .../AnimatorTransitionObservable.cs | 9 +++-- src/Avalonia.Animation/Clock.cs | 4 -- src/Avalonia.Animation/ClockBase.cs | 5 +-- src/Avalonia.Animation/TransitionInstance.cs | 37 +++++++++++++++++- .../TransitionObservableBase.cs | 39 ++++++++++++------- .../Transitions/DoubleTransition.cs | 14 ------- .../Transitions/FloatTransition.cs | 13 +------ .../Transitions/IntegerTransition.cs | 13 +------ .../Transitions/BoxShadowsTransition.cs | 13 +------ .../Animation/Transitions/ColorTransition.cs | 12 ++++++ .../Transitions/CornerRadiusTransition.cs | 13 +------ .../Animation/Transitions/PointTransition.cs | 13 +------ .../Animation/Transitions/SizeTransition.cs | 13 +------ .../Transitions/ThicknessTransition.cs | 13 +------ .../Animation/Transitions/VectorTransition.cs | 13 +------ .../Animations/TransitionBenchmark.cs | 37 +++++++++++++----- 17 files changed, 120 insertions(+), 146 deletions(-) create mode 100644 src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs diff --git a/src/Avalonia.Animation/AnimatorDrivenTransition.cs b/src/Avalonia.Animation/AnimatorDrivenTransition.cs index 3f6ac47556..88c8ec5ec1 100644 --- a/src/Avalonia.Animation/AnimatorDrivenTransition.cs +++ b/src/Avalonia.Animation/AnimatorDrivenTransition.cs @@ -3,6 +3,11 @@ using Avalonia.Animation.Animators; namespace Avalonia.Animation { + /// + /// using an to transition between values. + /// + /// Type of the transitioned value. + /// Type of the animator. public abstract class AnimatorDrivenTransition : Transition where TAnimator : Animator, new() { private static readonly TAnimator s_animator = new TAnimator(); diff --git a/src/Avalonia.Animation/AnimatorTransitionObservable.cs b/src/Avalonia.Animation/AnimatorTransitionObservable.cs index 10c19bb62b..3cc185179b 100644 --- a/src/Avalonia.Animation/AnimatorTransitionObservable.cs +++ b/src/Avalonia.Animation/AnimatorTransitionObservable.cs @@ -4,6 +4,11 @@ using Avalonia.Animation.Easings; namespace Avalonia.Animation { + /// + /// Transition observable based on an producing a value. + /// + /// Type of the transitioned value. + /// Type of the animator. public class AnimatorTransitionObservable : TransitionObservableBase where TAnimator : Animator { private readonly TAnimator _animator; @@ -11,7 +16,7 @@ namespace Avalonia.Animation private readonly T _oldValue; private readonly T _newValue; - public AnimatorTransitionObservable(TAnimator animator, IObservable progress, Easing easing, T oldValue, T newValue) : base(progress) + public AnimatorTransitionObservable(TAnimator animator, IObservable progress, Easing easing, T oldValue, T newValue) : base(progress, easing) { _animator = animator; _easing = easing; @@ -21,8 +26,6 @@ namespace Avalonia.Animation protected override T ProduceValue(double progress) { - progress = _easing.Ease(progress); - return _animator.Interpolate(progress, _oldValue, _newValue); } } diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index 6c7ae8349a..5c2b7ce0dd 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Reactive.Linq; -using System.Text; -using Avalonia.Reactive; namespace Avalonia.Animation { diff --git a/src/Avalonia.Animation/ClockBase.cs b/src/Avalonia.Animation/ClockBase.cs index 9fa6a85a6c..c6e5a363be 100644 --- a/src/Avalonia.Animation/ClockBase.cs +++ b/src/Avalonia.Animation/ClockBase.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Reactive.Linq; -using System.Text; using Avalonia.Reactive; namespace Avalonia.Animation @@ -58,7 +55,7 @@ namespace Avalonia.Animation return _observable.Subscribe(observer); } - private class ClockObservable : LightweightObservableBase + private sealed class ClockObservable : LightweightObservableBase { public bool HasSubscriptions { get; private set; } public void Pulse(TimeSpan time) => PublishNext(time); diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index 58dd71e8a5..b522d1961e 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.ExceptionServices; using Avalonia.Reactive; using Avalonia.Utilities; @@ -13,7 +14,7 @@ namespace Avalonia.Animation private TimeSpan _delay; private TimeSpan _duration; private readonly IClock _baseClock; - private IClock _clock; + private TransitionClock _clock; public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration) { @@ -71,7 +72,7 @@ namespace Avalonia.Animation protected override void Subscribed() { - _clock = new Clock(_baseClock); + _clock = new TransitionClock(_baseClock); _timerSubscription = _clock.Subscribe(this); PublishNext(0.0d); } @@ -90,5 +91,37 @@ namespace Avalonia.Animation { TimerTick(value); } + + /// + /// TODO: This clock is still fairly expensive due to implementation. + /// + private sealed class TransitionClock : ClockBase, IObserver + { + private readonly IDisposable _parentSubscription; + + public TransitionClock(IClock parent) + { + _parentSubscription = parent.Subscribe(this); + } + + protected override void Stop() + { + _parentSubscription.Dispose(); + } + + void IObserver.OnNext(TimeSpan value) + { + Pulse(value); + } + + void IObserver.OnCompleted() + { + } + + void IObserver.OnError(Exception error) + { + ExceptionDispatchInfo.Capture(error).Throw(); + } + } } } diff --git a/src/Avalonia.Animation/TransitionObservableBase.cs b/src/Avalonia.Animation/TransitionObservableBase.cs index 9f4fb8093c..c4ac803135 100644 --- a/src/Avalonia.Animation/TransitionObservableBase.cs +++ b/src/Avalonia.Animation/TransitionObservableBase.cs @@ -1,45 +1,58 @@ using System; +using Avalonia.Animation.Easings; using Avalonia.Reactive; #nullable enable namespace Avalonia.Animation { + /// + /// Provides base for observables implementing transitions. + /// + /// Type of the transitioned value. public abstract class TransitionObservableBase : SingleSubscriberObservableBase, IObserver { + private readonly Easing _easing; private readonly IObservable _progress; private IDisposable? _progressSubscription; - protected TransitionObservableBase(IObservable progress) + protected TransitionObservableBase(IObservable progress, Easing easing) { _progress = progress; + _easing = easing; } - void IObserver.OnCompleted() + /// + /// Produces value at given progress time point. + /// + /// Transition progress. + protected abstract T ProduceValue(double progress); + + protected override void Subscribed() { - PublishCompleted(); + _progressSubscription = _progress.Subscribe(this); } - void IObserver.OnError(Exception error) + protected override void Unsubscribed() { - PublishError(error); + _progressSubscription?.Dispose(); } - void IObserver.OnNext(double value) + void IObserver.OnCompleted() { - PublishNext(ProduceValue(value)); + PublishCompleted(); } - protected override void Unsubscribed() + void IObserver.OnError(Exception error) { - _progressSubscription?.Dispose(); + PublishError(error); } - protected override void Subscribed() + void IObserver.OnNext(double value) { - _progressSubscription = _progress.Subscribe(this); - } + double progress = _easing.Ease(value); - protected abstract T ProduceValue(double progress); + PublishNext(ProduceValue(progress)); + } } } diff --git a/src/Avalonia.Animation/Transitions/DoubleTransition.cs b/src/Avalonia.Animation/Transitions/DoubleTransition.cs index 28f630eb98..7232d87863 100644 --- a/src/Avalonia.Animation/Transitions/DoubleTransition.cs +++ b/src/Avalonia.Animation/Transitions/DoubleTransition.cs @@ -1,5 +1,3 @@ -using System; -using System.Reactive.Linq; using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -10,16 +8,4 @@ namespace Avalonia.Animation public class DoubleTransition : AnimatorDrivenTransition { } - - public class DoubleTransitionOld : Transition - { - private static readonly DoubleAnimator s_animator = new DoubleAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, double oldValue, double newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } - } } diff --git a/src/Avalonia.Animation/Transitions/FloatTransition.cs b/src/Avalonia.Animation/Transitions/FloatTransition.cs index 37b644fa96..a96db8ba5b 100644 --- a/src/Avalonia.Animation/Transitions/FloatTransition.cs +++ b/src/Avalonia.Animation/Transitions/FloatTransition.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive.Linq; - using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -8,15 +5,7 @@ namespace Avalonia.Animation /// /// Transition class that handles with types. /// - public class FloatTransition : Transition + public class FloatTransition : AnimatorDrivenTransition { - private static readonly FloatAnimator s_animator = new FloatAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, float oldValue, float newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Animation/Transitions/IntegerTransition.cs b/src/Avalonia.Animation/Transitions/IntegerTransition.cs index 223b2ba531..343da7b689 100644 --- a/src/Avalonia.Animation/Transitions/IntegerTransition.cs +++ b/src/Avalonia.Animation/Transitions/IntegerTransition.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive.Linq; - using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -8,15 +5,7 @@ namespace Avalonia.Animation /// /// Transition class that handles with types. /// - public class IntegerTransition : Transition + public class IntegerTransition : AnimatorDrivenTransition { - private static readonly Int32Animator s_animator = new Int32Animator(); - - /// - public override IObservable DoTransition(IObservable progress, int oldValue, int newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs index 008613fb40..8a070836e9 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/BoxShadowsTransition.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive.Linq; - using Avalonia.Animation.Animators; using Avalonia.Media; @@ -9,15 +6,7 @@ namespace Avalonia.Animation /// /// Transition class that handles with type. /// - public class BoxShadowsTransition : Transition + public class BoxShadowsTransition : AnimatorDrivenTransition { - private static readonly BoxShadowsAnimator s_animator = new BoxShadowsAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, BoxShadows oldValue, BoxShadows newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs new file mode 100644 index 0000000000..9b925a3779 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Transitions/ColorTransition.cs @@ -0,0 +1,12 @@ +using Avalonia.Animation.Animators; +using Avalonia.Media; + +namespace Avalonia.Animation +{ + /// + /// Transition class that handles with type. + /// + public class ColorTransition : AnimatorDrivenTransition + { + } +} diff --git a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs index 9ffdf53694..62b77a64e1 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive.Linq; - using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -8,15 +5,7 @@ namespace Avalonia.Animation /// /// Transition class that handles with type. /// - public class CornerRadiusTransition : Transition + public class CornerRadiusTransition : AnimatorDrivenTransition { - private static readonly CornerRadiusAnimator s_animator = new CornerRadiusAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, CornerRadius oldValue, CornerRadius newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs index fbe24c6d55..0985aaa8f8 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive.Linq; - using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -8,15 +5,7 @@ namespace Avalonia.Animation /// /// Transition class that handles with type. /// - public class PointTransition : Transition + public class PointTransition : AnimatorDrivenTransition { - private static readonly PointAnimator s_animator = new PointAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, Point oldValue, Point newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs index 464f83bec7..6e59fb3631 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive.Linq; - using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -8,15 +5,7 @@ namespace Avalonia.Animation /// /// Transition class that handles with type. /// - public class SizeTransition : Transition + public class SizeTransition : AnimatorDrivenTransition { - private static readonly SizeAnimator s_animator = new SizeAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, Size oldValue, Size newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs index 9fb3380780..f50929348f 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive.Linq; - using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -8,15 +5,7 @@ namespace Avalonia.Animation /// /// Transition class that handles with type. /// - public class ThicknessTransition : Transition + public class ThicknessTransition : AnimatorDrivenTransition { - private static readonly ThicknessAnimator s_animator = new ThicknessAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, Thickness oldValue, Thickness newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs index 5038117faa..596989fbae 100644 --- a/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs +++ b/src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs @@ -1,6 +1,3 @@ -using System; -using System.Reactive.Linq; - using Avalonia.Animation.Animators; namespace Avalonia.Animation @@ -8,15 +5,7 @@ namespace Avalonia.Animation /// /// Transition class that handles with type. /// - public class VectorTransition : Transition + public class VectorTransition : AnimatorDrivenTransition { - private static readonly VectorAnimator s_animator = new VectorAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, Vector oldValue, Vector newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } } } diff --git a/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs index 16aad0c21b..aba59d4585 100644 --- a/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Reactive.Linq; using System.Reactive.Subjects; using System.Runtime.CompilerServices; using Avalonia.Animation; +using Avalonia.Animation.Animators; using Avalonia.Layout; using BenchmarkDotNet.Attributes; @@ -14,26 +16,29 @@ namespace Avalonia.Benchmarks.Animations { private readonly DoubleTransition _transition; private readonly DoubleTransitionOld _oldTransition; - private readonly int _frameCount; private readonly Subject _timeProducer; private readonly List _producedValues; + private readonly AddValueObserver _observer; + + [Params(10, 100)] + public int FrameCount { get; set; } public TransitionBenchmark() { - _frameCount = 100; - _oldTransition = new DoubleTransitionOld { - Duration = TimeSpan.FromMilliseconds(_frameCount), Property = Layoutable.WidthProperty + Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty }; _transition = new DoubleTransition { - Duration = TimeSpan.FromMilliseconds(_frameCount), Property = Layoutable.WidthProperty + Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty }; _timeProducer = new Subject(); - _producedValues = new List(_frameCount); + _producedValues = new List(FrameCount); + + _observer = new AddValueObserver(_producedValues); } [Benchmark(Baseline = true)] @@ -56,14 +61,26 @@ namespace Avalonia.Benchmarks.Animations _producedValues.Clear(); - using var transitionSub = transitionObs.Subscribe(new AddValueObserver(_producedValues)); + using var transitionSub = transitionObs.Subscribe(_observer); - for (int i = 0; i < _frameCount; i++) + for (int i = 0; i < FrameCount; i++) { - _timeProducer.OnNext(TimeSpan.FromMilliseconds(i).TotalSeconds); + _timeProducer.OnNext(i/1000d); } - Debug.Assert(_producedValues.Count == _frameCount); + Debug.Assert(_producedValues.Count == FrameCount); + } + + private class DoubleTransitionOld : Transition + { + private static readonly DoubleAnimator s_animator = new DoubleAnimator(); + + /// + public override IObservable DoTransition(IObservable progress, double oldValue, double newValue) + { + return progress + .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); + } } private class AddValueObserver : IObserver From 78f7726289cf8a002c36f33ac49153e33407528b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 28 May 2021 17:09:14 +0200 Subject: [PATCH 7/9] Don't activate window when extending client area. Fixes #5988 --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 082aca1109..3a3342fd14 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -900,7 +900,7 @@ namespace Avalonia.Win32 IntPtr.Zero, rcWindow.left, rcWindow.top, rcClient.Width, rcClient.Height, - SetWindowPosFlags.SWP_FRAMECHANGED); + SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOACTIVATE); if (_isClientAreaExtended && WindowState != WindowState.FullScreen) { From a21880d4a8e8b51e51af6098a1b32d9727056576 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 30 May 2021 12:31:53 +0200 Subject: [PATCH 8/9] Remove old implementation benchmark. --- .../Animations/TransitionBenchmark.cs | 43 +++---------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs index aba59d4585..f74457a924 100644 --- a/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs @@ -14,22 +14,13 @@ namespace Avalonia.Benchmarks.Animations [MemoryDiagnoser] public class TransitionBenchmark { - private readonly DoubleTransition _transition; - private readonly DoubleTransitionOld _oldTransition; - private readonly Subject _timeProducer; - private readonly List _producedValues; private readonly AddValueObserver _observer; - - [Params(10, 100)] - public int FrameCount { get; set; } + private readonly List _producedValues; + private readonly Subject _timeProducer; + private readonly DoubleTransition _transition; public TransitionBenchmark() { - _oldTransition = new DoubleTransitionOld - { - Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty - }; - _transition = new DoubleTransition { Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty @@ -41,23 +32,13 @@ namespace Avalonia.Benchmarks.Animations _observer = new AddValueObserver(_producedValues); } - [Benchmark(Baseline = true)] - [MethodImpl(MethodImplOptions.NoInlining)] - public void OldTransition() - { - TransitionCommon(_oldTransition); - } + [Params(10, 100)] public int FrameCount { get; set; } [Benchmark] [MethodImpl(MethodImplOptions.NoInlining)] public void NewTransition() { - TransitionCommon(_transition); - } - - private void TransitionCommon(Transition transition) - { - var transitionObs = transition.DoTransition(_timeProducer, 0, 1); + var transitionObs = _transition.DoTransition(_timeProducer, 0, 1); _producedValues.Clear(); @@ -65,24 +46,12 @@ namespace Avalonia.Benchmarks.Animations for (int i = 0; i < FrameCount; i++) { - _timeProducer.OnNext(i/1000d); + _timeProducer.OnNext(i / 1000d); } Debug.Assert(_producedValues.Count == FrameCount); } - private class DoubleTransitionOld : Transition - { - private static readonly DoubleAnimator s_animator = new DoubleAnimator(); - - /// - public override IObservable DoTransition(IObservable progress, double oldValue, double newValue) - { - return progress - .Select(progress => s_animator.Interpolate(Easing.Ease(progress), oldValue, newValue)); - } - } - private class AddValueObserver : IObserver { private readonly List _values; From 86b9a60a4c2c037b32674a65cd01796cfab7727f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 31 May 2021 09:41:49 -0700 Subject: [PATCH 9/9] Fix custom hit testing (#5968) * Fix custom hit testing * Use ICustomHitTest as it's intended to be used in defered renderer Co-authored-by: Dan Walmsley Co-authored-by: Steven Kirk Co-authored-by: Steven Kirk --- src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs | 3 +-- src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs index 1d655bb691..74d804f2bf 100644 --- a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs @@ -52,8 +52,7 @@ namespace Avalonia.Controls.Primitives { if (InputPassThroughElement is object) { - var p = point.Transform(this.TransformToVisual(VisualRoot)!.Value); - var hit = VisualRoot.GetVisualAt(p, x => x != this); + var hit = VisualRoot.GetVisualAt(point, x => x != this); if (hit is object) { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index 36aa08c2f9..d8e5baac97 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -257,7 +257,7 @@ namespace Avalonia.Rendering.SceneGraph if (childCount == 0 || wasVisited) { if ((wasVisited || FilterAndClip(node, ref clip)) && - (node.Visual is ICustomSimpleHitTest custom ? custom.HitTest(_point) : node.HitTest(_point))) + (node.Visual is ICustomHitTest custom ? custom.HitTest(_point) : node.HitTest(_point))) { _current = node.Visual;