Browse Source

Avoid closures and extra allocations caused by transition observables.

pull/5953/head
Dariusz Komosinski 5 years ago
parent
commit
5765007ecd
  1. 15
      src/Avalonia.Animation/AnimatorDrivenTransition.cs
  2. 24
      src/Avalonia.Animation/AnimatorTransitionObservable.cs
  3. 6
      src/Avalonia.Animation/Transition.cs
  4. 23
      src/Avalonia.Animation/TransitionInstance.cs
  5. 45
      src/Avalonia.Animation/TransitionObservableBase.cs
  6. 7
      src/Avalonia.Animation/Transitions/DoubleTransition.cs
  7. 18
      src/Avalonia.Visuals/Animation/Transitions/TransformOperationsTransition.cs
  8. 92
      tests/Avalonia.Benchmarks/Animations/TransitionBenchmark.cs

15
src/Avalonia.Animation/AnimatorDrivenTransition.cs

@ -0,0 +1,15 @@
using System;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
public class AnimatorDrivenTransition<T, TAnimator> : Transition<T> where TAnimator : Animator<T>, new()
{
private static readonly TAnimator s_animator = new TAnimator();
public override IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue)
{
return new AnimatorTransitionObservable<T, TAnimator>(s_animator, progress, oldValue, newValue);
}
}
}

24
src/Avalonia.Animation/AnimatorTransitionObservable.cs

@ -0,0 +1,24 @@
using System;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
public class AnimatorTransitionObservable<T, TAnimator> : TransitionObservableBase<T> where TAnimator : Animator<T>
{
private readonly TAnimator _animator;
private readonly T _oldValue;
private readonly T _newValue;
public AnimatorTransitionObservable(TAnimator animator, IObservable<double> 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);
}
}
}

6
src/Avalonia.Animation/Transition`1.cs → 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<T>((AvaloniaProperty<T>)Property, transition, Data.BindingPriority.Animation);
}
}
}
}

23
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
/// <summary>
/// Handles the timing and lifetime of a <see cref="Transition{T}"/>.
/// </summary>
internal class TransitionInstance : SingleSubscriberObservableBase<double>
internal class TransitionInstance : SingleSubscriberObservableBase<double>, IObserver<TimeSpan>
{
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<TimeSpan>.OnCompleted()
{
PublishCompleted();
}
void IObserver<TimeSpan>.OnError(Exception error)
{
PublishError(error);
}
void IObserver<TimeSpan>.OnNext(TimeSpan value)
{
TimerTick(value);
}
}
}

45
src/Avalonia.Animation/TransitionObservableBase.cs

@ -0,0 +1,45 @@
using System;
using Avalonia.Reactive;
#nullable enable
namespace Avalonia.Animation
{
public abstract class TransitionObservableBase<T> : SingleSubscriberObservableBase<T>, IObserver<double>
{
private readonly IObservable<double> _progress;
private IDisposable? _progressSubscription;
protected TransitionObservableBase(IObservable<double> 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<double>.OnCompleted()
{
PublishCompleted();
}
void IObserver<double>.OnError(Exception error)
{
PublishError(error);
}
void IObserver<double>.OnNext(double value)
{
PublishNext(ProduceValue(value));
}
}
}

7
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
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="double"/> types.
/// </summary>
public class DoubleTransition : Transition<double>
public class DoubleTransition : AnimatorDrivenTransition<double, DoubleAnimator>
{
}
public class DoubleTransitionOld : Transition<double>
{
private static readonly DoubleAnimator s_animator = new DoubleAnimator();

18
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<ITransform>
{
private static readonly TransformOperationsAnimator _operationsAnimator = new TransformOperationsAnimator();
private static readonly TransformOperationsAnimator s_operationsAnimator = new TransformOperationsAnimator();
public override IObservable<ITransform> DoTransition(IObservable<double> progress,
public override IObservable<ITransform> DoTransition(
IObservable<double> 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<TransformOperations, TransformOperationsAnimator>(
s_operationsAnimator, progress, oldTransform, newTransform);
}
}
}

92
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<double> _timeProducer;
private readonly List<double> _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<double>();
_producedValues = new List<double>(_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<double> 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<double>
{
private readonly List<double> _values;
public AddValueObserver(List<double> values)
{
_values = values;
}
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(double value)
{
_values.Add(value);
}
}
}
}
Loading…
Cancel
Save