Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions Microsoft.Toolkit.Diagnostics/Microsoft.Toolkit.Diagnostics.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard1.4;netstandard2.0;netstandard2.1;net5.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<Title>Windows Community Toolkit Diagnostics .NET Standard</Title>
<Description>
This package includes .NET Standard code only helpers such as:
- Guard: Helper methods to verify conditions when running code.
- ThrowHelper: Helper methods to efficiently throw exceptions.
</Description>
<PackageTags>UWP Toolkit Windows IncrementalLoadingCollection String Array extensions helpers</PackageTags>
</PropertyGroup>
<Choose>
<When Condition=" '$(TargetFramework)' == 'netstandard1.4' ">
<ItemGroup>

<!-- .NET Standard 1.4 doesn't have the Span<T> type, ValueTuple or the [Pure] attribute -->
<PackageReference Include="System.Diagnostics.Contracts" Version="4.3.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
</When>
<When Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<ItemGroup>

<!-- .NET Standard 2.0 doesn't have the Span<T> type -->
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
</When>
<When Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<PropertyGroup>
<DefineConstants>NETSTANDARD2_1_OR_GREATER</DefineConstants>
</PropertyGroup>
<ItemGroup>

<!-- .NET Standard 2.1 doesn't have the Unsafe type -->
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
</ItemGroup>
</When>
<When Condition=" '$(TargetFramework)' == 'net5.0' ">
<PropertyGroup>
<DefineConstants>NETSTANDARD2_1_OR_GREATER</DefineConstants>
</PropertyGroup>
</When>
</Choose>

<!-- T4 service used by the Guard APIs -->
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

using System;
using System.Numerics;
using Microsoft.Toolkit.Diagnostics;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
Expand Down Expand Up @@ -605,7 +604,7 @@ public AnimationBuilder Transform(
{
if (!Matrix4x4.Decompose(to, out Vector3 toScale, out Quaternion toRotation, out Vector3 toTranslation))
{
ThrowHelper.ThrowArgumentException("The destination matrix could not be decomposed");
ThrowThrowArgumentExceptionForToDecompose();
}

Vector3? fromScale = null;
Expand All @@ -616,7 +615,7 @@ public AnimationBuilder Transform(
{
if (!Matrix4x4.Decompose(from.GetValueOrDefault(), out Vector3 scale3, out Quaternion rotation4, out Vector3 translation3))
{
ThrowHelper.ThrowArgumentException("The initial matrix could not be decomposed");
ThrowThrowArgumentExceptionForFromDecompose();
}

fromScale = scale3;
Expand All @@ -629,6 +628,9 @@ public AnimationBuilder Transform(
Translation(toTranslation, fromTranslation, delay, duration, easingType, easingMode);

return this;

static void ThrowThrowArgumentExceptionForToDecompose() => throw new ArgumentException("The destination matrix could not be decomposed");
static void ThrowThrowArgumentExceptionForFromDecompose() => throw new ArgumentException("The initial matrix could not be decomposed");
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.Diagnostics;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Composition;
Expand Down Expand Up @@ -161,7 +160,7 @@ public CompositionAnimation GetAnimation(CompositionObject targetHint, out Compo
easingFunction);
}

return ThrowHelper.ThrowInvalidOperationException<CompositionAnimation>("Invalid animation type");
throw new InvalidOperationException("Invalid animation type");
}

/// <inheritdoc/>
Expand Down Expand Up @@ -216,7 +215,7 @@ public Timeline GetAnimation(DependencyObject targetHint)
easingFunction);
}

return ThrowHelper.ThrowInvalidOperationException<Timeline>("Invalid animation type");
throw new InvalidOperationException("Invalid animation type");
}

/// <summary>
Expand All @@ -229,9 +228,33 @@ public Timeline GetAnimation(DependencyObject targetHint)
private TValue GetToAs<TValue>()
where TValue : unmanaged
{
T to = To;

return Unsafe.As<T, TValue>(ref to);
// We employ this (T2)(object)t1 pattern multiple times in this library to alter generics.
// This is an equivalent but safer alternative to using Unsafe.As<TFrom, TTo>(ref TFrom).
// For instance, this method will result in the following IL being emitted:
// =============================
// IL_0000: ldarg.0
// IL_0001: call instance !0 class AnimationFactory`1<!T>::get_To()
// IL_0006: box !T
// IL_000b: unbox.any !!TValue
// IL_0010: ret
// =============================
// The key point is that the JIT (and AOT compilers such as .NET Native) can recognize this
// pattern and optimize the boxing away in case the types match. This is the case whenever
// the generic arguments are value types, which due to generic types in .NET being reified
// results in a completely different generic instantiation of the same method, making the
// type arguments effectively constant values known at compile time, ie. at JIT time.
// As a result of this, the boxing is completely avoided and the value is returned directly.
// Leveraging this pattern lets us keep the same optimal codegen while avoiding the extra
// NuGet package dependency on UWP, and the more dangerous path using the Unsafe APIs.
// As an example, assuming T is float, the JIT will produce the following codegen on x64:
// =============================
// L0000: vzeroupper
// L0003: vmovss xmm0, [rcx+8]
// L0008: ret
// =============================
// We can see how the property value is loaded directly from the underlying field and
// then returned to the caller: no boxing or unwanted overhead is introduced at all.
return (TValue)(object)To;
}

/// <summary>
Expand All @@ -251,7 +274,7 @@ private TValue GetToAs<TValue>()

T from = From.GetValueOrDefault();

return Unsafe.As<T, TValue>(ref from);
return (TValue)(object)from;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Microsoft.Toolkit.Uwp.UI.Animations.Builders.Helpers
{
/// <summary>
/// A small generic builder type that allows to create <see cref="ReadOnlySpan{T}"/> instances.
/// A small generic builder type that allows to create <see cref="ArraySegment{T}"/> instances.
/// </summary>
/// <typeparam name="T">The type of items to create a sequence of.</typeparam>
internal struct ListBuilder<T>
Expand Down Expand Up @@ -56,14 +56,14 @@ public void Append(T item)
}

/// <summary>
/// Gets a <see cref="ReadOnlySpan{T}"/> instance with the current items.
/// Gets a <see cref="ArraySegment{T}"/> instance with the current items.
/// </summary>
/// <returns>A <see cref="ReadOnlySpan{T}"/> instance with the current items.</returns>
/// <returns>A <see cref="ArraySegment{T}"/> instance with the current items.</returns>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<T> AsSpan()
public ArraySegment<T> GetArraySegment()
{
return this.array.AsSpan(0, this.index);
return new(this.array, 0, this.index);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using System;
using System.Numerics;
using Microsoft.Toolkit.Diagnostics;
using Windows.UI;
using Windows.UI.Composition;
using Windows.UI.Xaml.Media.Animation;
Expand Down Expand Up @@ -33,7 +32,7 @@ public static CompositionAnimation GetAnimation<TKeyFrame>(
TimeSpan? delay,
TimeSpan duration,
RepeatOption repeat,
ReadOnlySpan<TKeyFrame> keyFrames)
ArraySegment<TKeyFrame> keyFrames)
where TKeyFrame : struct, IKeyFrameInfo
{
KeyFrameAnimation animation;
Expand All @@ -42,7 +41,7 @@ public static CompositionAnimation GetAnimation<TKeyFrame>(
{
BooleanKeyFrameAnimation boolAnimation = target.Compositor.CreateBooleanKeyFrameAnimation();

foreach (ref readonly var keyFrame in keyFrames)
foreach (var keyFrame in keyFrames)
{
if (keyFrame.TryInsertExpressionKeyFrame(boolAnimation, duration))
{
Expand All @@ -58,7 +57,7 @@ public static CompositionAnimation GetAnimation<TKeyFrame>(
{
ScalarKeyFrameAnimation scalarAnimation = target.Compositor.CreateScalarKeyFrameAnimation();

foreach (ref readonly var keyFrame in keyFrames)
foreach (var keyFrame in keyFrames)
{
if (keyFrame.TryInsertExpressionKeyFrame(scalarAnimation, duration))
{
Expand All @@ -83,7 +82,7 @@ public static CompositionAnimation GetAnimation<TKeyFrame>(
{
ScalarKeyFrameAnimation scalarAnimation = target.Compositor.CreateScalarKeyFrameAnimation();

foreach (ref readonly var keyFrame in keyFrames)
foreach (var keyFrame in keyFrames)
{
if (keyFrame.TryInsertExpressionKeyFrame(scalarAnimation, duration))
{
Expand All @@ -108,7 +107,7 @@ public static CompositionAnimation GetAnimation<TKeyFrame>(
{
Vector2KeyFrameAnimation vector2Animation = target.Compositor.CreateVector2KeyFrameAnimation();

foreach (ref readonly var keyFrame in keyFrames)
foreach (var keyFrame in keyFrames)
{
if (keyFrame.TryInsertExpressionKeyFrame(vector2Animation, duration))
{
Expand All @@ -133,7 +132,7 @@ public static CompositionAnimation GetAnimation<TKeyFrame>(
{
Vector3KeyFrameAnimation vector3Animation = target.Compositor.CreateVector3KeyFrameAnimation();

foreach (ref readonly var keyFrame in keyFrames)
foreach (var keyFrame in keyFrames)
{
if (keyFrame.TryInsertExpressionKeyFrame(vector3Animation, duration))
{
Expand All @@ -158,7 +157,7 @@ public static CompositionAnimation GetAnimation<TKeyFrame>(
{
Vector4KeyFrameAnimation vector4Animation = target.Compositor.CreateVector4KeyFrameAnimation();

foreach (ref readonly var keyFrame in keyFrames)
foreach (var keyFrame in keyFrames)
{
if (keyFrame.TryInsertExpressionKeyFrame(vector4Animation, duration))
{
Expand All @@ -183,7 +182,7 @@ public static CompositionAnimation GetAnimation<TKeyFrame>(
{
ColorKeyFrameAnimation colorAnimation = target.Compositor.CreateColorKeyFrameAnimation();

foreach (ref readonly var keyFrame in keyFrames)
foreach (var keyFrame in keyFrames)
{
if (keyFrame.TryInsertExpressionKeyFrame(colorAnimation, duration))
{
Expand All @@ -208,7 +207,7 @@ public static CompositionAnimation GetAnimation<TKeyFrame>(
{
QuaternionKeyFrameAnimation quaternionAnimation = target.Compositor.CreateQuaternionKeyFrameAnimation();

foreach (ref readonly var keyFrame in keyFrames)
foreach (var keyFrame in keyFrames)
{
if (keyFrame.TryInsertExpressionKeyFrame(quaternionAnimation, duration))
{
Expand All @@ -231,7 +230,7 @@ public static CompositionAnimation GetAnimation<TKeyFrame>(
}
else
{
return ThrowHelper.ThrowInvalidOperationException<CompositionAnimation>("Invalid animation type");
throw new InvalidOperationException("Invalid animation type");
}

animation.Duration = duration;
Expand Down Expand Up @@ -284,7 +283,7 @@ public CompositionAnimation GetAnimation(CompositionObject targetHint, out Compo
this.delay,
this.duration,
this.repeat,
this.keyFrames.AsSpan());
this.keyFrames.GetArraySegment());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public Timeline GetAnimation(DependencyObject targetHint)
this.delay,
this.duration,
this.repeat,
this.keyFrames.AsSpan());
this.keyFrames.GetArraySegment());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public KeyFrameInfo(
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue GetValueAs<TValue>()
{
return Unsafe.As<T, TValue>(ref Unsafe.AsRef(in this.value));
return (TValue)(object)this.value;
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public CompositionAnimation GetAnimation(CompositionObject targetHint, out Compo

// We can retrieve the total duration from the last timed keyframe, and then set
// this as the target duration and use it to normalize the keyframe progresses.
ReadOnlySpan<KeyFrameInfo> keyFrames = this.keyFrames.AsSpan();
TimeSpan duration = keyFrames[keyFrames.Length - 1].GetTimedProgress(default);
ArraySegment<KeyFrameInfo> keyFrames = this.keyFrames.GetArraySegment();
TimeSpan duration = keyFrames[keyFrames.Count - 1].GetTimedProgress(default);

return NormalizedKeyFrameAnimationBuilder<T>.GetAnimation(
targetHint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.

using System;
using Microsoft.Toolkit.Diagnostics;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
Expand Down Expand Up @@ -32,7 +31,7 @@ public static Timeline GetAnimation<TKeyFrame>(
TimeSpan? delay,
TimeSpan duration,
RepeatOption repeat,
ReadOnlySpan<TKeyFrame> keyFrames)
ArraySegment<TKeyFrame> keyFrames)
where TKeyFrame : struct, IKeyFrameInfo
{
Timeline animation;
Expand Down Expand Up @@ -118,7 +117,9 @@ public static Timeline GetAnimation<TKeyFrame>(
}
else
{
return ThrowHelper.ThrowInvalidOperationException<Timeline>("Invalid animation type");
static Timeline ThrowInvalidOperationException() => throw new InvalidOperationException("Invalid animation type");

return ThrowInvalidOperationException();
}

animation.BeginTime = delay;
Expand Down Expand Up @@ -163,7 +164,7 @@ public Timeline GetAnimation(DependencyObject targetHint)
this.delay,
default,
this.repeat,
this.keyFrames.AsSpan());
this.keyFrames.GetArraySegment());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public KeyFrameInfo(
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue GetValueAs<TValue>()
{
return Unsafe.As<T, TValue>(ref Unsafe.AsRef(in this.value));
return (TValue)(object)this.value;
}

/// <inheritdoc/>
Expand Down
Loading