Skip to content

Commit 3818924

Browse files
committed
feat(perf): Improve perf of the ObjectAnimationUsingKeyFrame
1 parent aaba63f commit 3818924

File tree

8 files changed

+783
-335
lines changed

8 files changed

+783
-335
lines changed

src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Animation/Given_ObjectAnimationUsingKeyFrames.cs

Lines changed: 282 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
using System;
1+
#if !NETFX_CORE // Disabled on UWP for now because 17763 doesn't support WinUI 2.x
2+
using System;
23
using System.Collections.Generic;
34
using System.Linq;
45
using System.Text;
6+
using System.Threading;
57
using System.Threading.Tasks;
68
using Microsoft.VisualStudio.TestTools.UnitTesting;
79
using Private.Infrastructure;
@@ -10,14 +12,16 @@
1012
using Windows.UI.Xaml;
1113
using Windows.UI.Xaml.Controls;
1214
using Windows.UI.Xaml.Media;
15+
using Windows.UI.Xaml.Media.Animation;
16+
using FluentAssertions;
1317

1418
namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Media_Animation
1519
{
1620
[TestClass]
1721
[RunsOnUIThread]
1822
public class Given_ObjectAnimationUsingKeyFrames
1923
{
20-
#if !NETFX_CORE // Disabled on UWP for now because 17763 doesn't support WinUI 2.x
24+
2125
[TestMethod]
2226
public async Task When_Theme_Changed_Animated_Value()
2327
{
@@ -42,7 +46,235 @@ public async Task When_Theme_Changed_Animated_Value()
4246
}
4347
}
4448
}
45-
#endif
49+
50+
[TestMethod]
51+
public async Task When_Animate()
52+
{
53+
var ct = CancellationToken.None; // Not supported yet by test engine
54+
55+
object v1, v2, v3;
56+
var target = new AnimTarget();
57+
var sut = new ObjectAnimationUsingKeyFrames
58+
{
59+
BeginTime = TimeSpan.Zero,
60+
RepeatBehavior = new RepeatBehavior(),
61+
KeyFrames =
62+
{
63+
new ObjectKeyFrame{KeyTime = TimeSpan.Zero, Value = v1 = new object()},
64+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(1), Value = v2 = new object()},
65+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(2), Value = v3 = new object()},
66+
}
67+
};
68+
69+
Storyboard.SetTarget(sut, target);
70+
Storyboard.SetTargetProperty(sut, nameof(target.Value));
71+
72+
((ITimeline)sut).Begin();
73+
await target.GetValue(ct, 3);
74+
await Task.Yield();
75+
76+
target.History.Should().BeEquivalentTo(v1, v2, v3);
77+
sut.State.Should().Be(Timeline.TimelineState.Stopped);
78+
}
79+
80+
[TestMethod]
81+
public async Task When_Stop()
82+
{
83+
var ct = CancellationToken.None; // Not supported yet by test engine
84+
85+
object v1, v2, v3;
86+
var target = new AnimTarget();
87+
var sut = new ObjectAnimationUsingKeyFrames
88+
{
89+
BeginTime = TimeSpan.Zero,
90+
RepeatBehavior = new RepeatBehavior(),
91+
KeyFrames =
92+
{
93+
new ObjectKeyFrame{KeyTime = TimeSpan.Zero, Value = v1 = new object()},
94+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(1), Value = v2 = new object()},
95+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(10), Value = v3 = new object()},
96+
}
97+
};
98+
99+
Storyboard.SetTarget(sut, target);
100+
Storyboard.SetTargetProperty(sut, nameof(target.Value));
101+
102+
((ITimeline)sut).Begin();
103+
await target.GetValue(ct, 2);
104+
await Task.Yield();
105+
((ITimeline)sut).Stop();
106+
await Task.Delay(100, ct);
107+
108+
target.History.Should().BeEquivalentTo(v1, v2);
109+
sut.State.Should().Be(Timeline.TimelineState.Stopped);
110+
}
111+
112+
[TestMethod]
113+
public async Task When_PauseResume()
114+
{
115+
var ct = CancellationToken.None; // Not supported yet by test engine
116+
117+
object v1, v2, v3;
118+
var target = new AnimTarget();
119+
var sut = new ObjectAnimationUsingKeyFrames
120+
{
121+
BeginTime = TimeSpan.Zero,
122+
RepeatBehavior = new RepeatBehavior(),
123+
KeyFrames =
124+
{
125+
new ObjectKeyFrame{KeyTime = TimeSpan.Zero, Value = v1 = new object()},
126+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(1), Value = v2 = new object()},
127+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(10), Value = v3 = new object()},
128+
}
129+
};
130+
131+
Storyboard.SetTarget(sut, target);
132+
Storyboard.SetTargetProperty(sut, nameof(target.Value));
133+
134+
((ITimeline)sut).Begin();
135+
await target.GetValue(ct, 2);
136+
await Task.Yield();
137+
((ITimeline)sut).Pause();
138+
139+
await Task.Delay(100, ct);
140+
target.History.Should().BeEquivalentTo(v1, v2);
141+
sut.State.Should().Be(Timeline.TimelineState.Paused);
142+
143+
((ITimeline)sut).Resume();
144+
await target.GetValue(ct, 2);
145+
await Task.Yield();
146+
147+
target.History.Should().BeEquivalentTo(v1, v2, v3);
148+
sut.State.Should().Be(Timeline.TimelineState.Stopped);
149+
}
150+
151+
[TestMethod]
152+
public async Task When_RepeatCount()
153+
{
154+
var ct = CancellationToken.None; // Not supported yet by test engine
155+
156+
object v1, v2, v3;
157+
var target = new AnimTarget();
158+
var sut = new ObjectAnimationUsingKeyFrames
159+
{
160+
BeginTime = TimeSpan.Zero,
161+
RepeatBehavior = new RepeatBehavior(3),
162+
KeyFrames =
163+
{
164+
new ObjectKeyFrame{KeyTime = TimeSpan.Zero, Value = v1 = new object()},
165+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(1), Value = v2 = new object()},
166+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(2), Value = v3 = new object()},
167+
}
168+
};
169+
170+
Storyboard.SetTarget(sut, target);
171+
Storyboard.SetTargetProperty(sut, nameof(target.Value));
172+
173+
((ITimeline)sut).Begin();
174+
await target.GetValue(ct, 9);
175+
await Task.Yield();
176+
177+
target.History.Should().BeEquivalentTo(v1, v2, v3, v1, v2, v3, v1, v2, v3);
178+
sut.State.Should().Be(Timeline.TimelineState.Stopped);
179+
}
180+
181+
[TestMethod]
182+
public async Task When_RepeatDuration()
183+
{
184+
var ct = CancellationToken.None; // Not supported yet by test engine
185+
186+
object v1, v2, v3;
187+
var target = new AnimTarget();
188+
var sut = new ObjectAnimationUsingKeyFrames
189+
{
190+
BeginTime = TimeSpan.Zero,
191+
RepeatBehavior = new RepeatBehavior(TimeSpan.FromMilliseconds(2 * 3)),
192+
KeyFrames =
193+
{
194+
new ObjectKeyFrame{KeyTime = TimeSpan.Zero, Value = v1 = new object()},
195+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(1), Value = v2 = new object()},
196+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(2), Value = v3 = new object()},
197+
}
198+
};
199+
200+
Storyboard.SetTarget(sut, target);
201+
Storyboard.SetTargetProperty(sut, nameof(target.Value));
202+
203+
((ITimeline)sut).Begin();
204+
await target.GetValue(ct, 9);
205+
await Task.Yield();
206+
207+
target.History.Should().BeEquivalentTo(v1, v2, v3, v1, v2, v3, v1, v2, v3);
208+
sut.State.Should().Be(Timeline.TimelineState.Stopped);
209+
}
210+
211+
[TestMethod]
212+
public async Task When_RepeatForever()
213+
{
214+
var ct = CancellationToken.None; // Not supported yet by test engine
215+
216+
object v1, v2, v3;
217+
var target = new AnimTarget();
218+
var sut = new ObjectAnimationUsingKeyFrames
219+
{
220+
BeginTime = TimeSpan.Zero,
221+
RepeatBehavior = RepeatBehavior.Forever,
222+
KeyFrames =
223+
{
224+
new ObjectKeyFrame{KeyTime = TimeSpan.Zero, Value = v1 = new object()},
225+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(1), Value = v2 = new object()},
226+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(2), Value = v3 = new object()},
227+
}
228+
};
229+
230+
Storyboard.SetTarget(sut, target);
231+
Storyboard.SetTargetProperty(sut, nameof(target.Value));
232+
233+
((ITimeline)sut).Begin();
234+
await target.GetValue(ct, 9);
235+
await Task.Yield();
236+
237+
try
238+
{
239+
target.History.Should().BeEquivalentTo(v1, v2, v3, v1, v2, v3, v1, v2, v3);
240+
sut.State.Should().Be(Timeline.TimelineState.Active);
241+
}
242+
finally
243+
{
244+
((ITimeline)sut).Begin();
245+
}
246+
}
247+
248+
[TestMethod]
249+
public async Task When_BeginTime()
250+
{
251+
var ct = CancellationToken.None; // Not supported yet by test engine
252+
253+
object v1, v2, v3;
254+
var target = new AnimTarget();
255+
var sut = new ObjectAnimationUsingKeyFrames
256+
{
257+
BeginTime = TimeSpan.FromMilliseconds(10),
258+
RepeatBehavior = new RepeatBehavior(),
259+
KeyFrames =
260+
{
261+
new ObjectKeyFrame{KeyTime = TimeSpan.Zero, Value = v1 = new object()},
262+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(1), Value = v2 = new object()},
263+
new ObjectKeyFrame{KeyTime = TimeSpan.FromMilliseconds(2), Value = v3 = new object()},
264+
}
265+
};
266+
267+
Storyboard.SetTarget(sut, target);
268+
Storyboard.SetTargetProperty(sut, nameof(target.Value));
269+
270+
((ITimeline)sut).Begin();
271+
await Task.Delay(5, ct);
272+
((ITimeline)sut).Stop();
273+
274+
target.History.Should().BeEquivalentTo(/* empty */);
275+
sut.State.Should().Be(Timeline.TimelineState.Stopped);
276+
}
277+
46278

47279
/// <summary>
48280
/// Ensure dark theme is applied for the course of a single test.
@@ -57,6 +289,52 @@ public async Task When_Theme_Changed_Animated_Value()
57289
#endif
58290
}
59291

292+
public partial class AnimTarget : DependencyObject
293+
{
294+
private event EventHandler _valuedAdded;
295+
private object _value;
296+
297+
public object Value
298+
{
299+
get => _value;
300+
set
301+
{
302+
_value = value;
303+
History.Add(value);
304+
_valuedAdded?.Invoke(this, EventArgs.Empty);
305+
}
306+
}
307+
308+
public List<object> History { get; } = new List<object>();
309+
310+
public async Task<object> GetValue(CancellationToken ct, int count, TimeSpan? timeout = null)
311+
{
312+
var cts = CancellationTokenSource.CreateLinkedTokenSource(
313+
ct,
314+
new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(1)).Token);
315+
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.AttachedToParent);
316+
317+
using var _ = cts.Token.Register(() => tcs.TrySetCanceled());
318+
try
319+
{
320+
_valuedAdded += OnValueAdded;
321+
return await tcs.Task;
322+
}
323+
finally
324+
{
325+
_valuedAdded -= OnValueAdded;
326+
}
327+
328+
void OnValueAdded(object snd, EventArgs eventArgs)
329+
{
330+
if (History.Count > count)
331+
{
332+
tcs.TrySetResult(History[count - 1]);
333+
}
334+
}
335+
}
336+
}
337+
60338
public partial class MyCheckBox : CheckBox
61339
{
62340
public ContentPresenter ContentPresenter { get; set; }
@@ -67,3 +345,4 @@ protected override void OnApplyTemplate()
67345
}
68346
}
69347
}
348+
#endif
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
using System.Linq;
3+
4+
namespace Windows.UI.Xaml.Media.Animation
5+
{
6+
internal interface IKeyFrame
7+
{
8+
public KeyTime KeyTime { get; }
9+
}
10+
11+
internal interface IKeyFrame<out TValue> : IKeyFrame
12+
{
13+
public TValue Value { get; }
14+
}
15+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#nullable enable
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
namespace Windows.UI.Xaml.Media.Animation
8+
{
9+
internal class KeyFrameComparer : IComparer<IKeyFrame>
10+
{
11+
public static KeyFrameComparer Instance { get; } = new KeyFrameComparer();
12+
13+
private KeyFrameComparer()
14+
{
15+
}
16+
17+
/// <inheritdoc />
18+
public int Compare(IKeyFrame x, IKeyFrame y)
19+
=> ((IComparable<KeyTime>)x.KeyTime).CompareTo(y.KeyTime);
20+
}
21+
22+
internal class KeyFrameComparer<TValue> : IComparer<IKeyFrame<TValue>>
23+
{
24+
public static KeyFrameComparer<TValue> Instance { get; } = new KeyFrameComparer<TValue>();
25+
26+
private KeyFrameComparer()
27+
{
28+
}
29+
30+
/// <inheritdoc />
31+
public int Compare(IKeyFrame<TValue> x, IKeyFrame<TValue> y)
32+
=> ((IComparable<KeyTime>)x.KeyTime).CompareTo(y.KeyTime);
33+
}
34+
}

0 commit comments

Comments
 (0)