diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/ExpressionNode.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/ExpressionNode.cs index 0b2bcb016cc..e02d4500201 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/ExpressionNode.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Expressions/ExpressionNodes/ExpressionNode.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Numerics; +using System.Runtime.CompilerServices; using Windows.UI; using Windows.UI.Composition; @@ -283,10 +284,10 @@ internal void EnsureReferenceInfo() // Create a map to store the generated paramNames for each CompObj _compObjToParamNameMap = new Dictionary(); - var paramCount = 0; + var paramCount = 0u; foreach (var compObj in compObjects) { - string paramName = UniqueParamNameFromIndex(paramCount++); // Guid.NewGuid().ToUppercaseAsciiLetters(); + string paramName = CreateUniqueParamNameFromIndex(paramCount++); _compObjToParamNameMap.Add(compObj, paramName); } @@ -314,20 +315,35 @@ internal void EnsureReferenceInfo() } } - // Generates Excel-column-like identifiers, e.g. A, B, ..., Z, AA, AB... - string UniqueParamNameFromIndex(int i) + // Generates Excel-column-like identifiers, e.g. A, B, ..., Z, AA, BA... + // This implementation aggregates characters in reverse order to avoid having to + // precompute the exact number of characters in the resulting string. This is not + // important in this context as the only critical property to maintain is to have + // a unique mapping to each input value to the resulting sequence of letters. + [SkipLocalsInit] + static unsafe string CreateUniqueParamNameFromIndex(uint i) { - var alphabetLength = 'Z' - 'A' + 1; - var paramName = ((char)('A' + (i % alphabetLength))).ToString(); + const int alphabetLength = 'Z' - 'A' + 1; - while (i / alphabetLength > 0) + // The total length of the resulting sequence is guaranteed to always + // be less than 8, given that log26(4294967295) ≈ 6.8. In this case we + // are just allocating the immediate next power of two following that. + // Note: this is using a char* buffer instead of Span as the latter + // is not referenced here, and we don't want to pull in an extra package. + char* characters = stackalloc char[8]; + + characters[0] = (char)('A' + (i % alphabetLength)); + + int totalCharacters = 1; + + while ((i /= alphabetLength) > 0) { - i = (i / alphabetLength) - 1; - var nextCharacter = (char)('A' + (i % alphabetLength)); - paramName = nextCharacter + paramName; + i--; + + characters[totalCharacters++] = (char)('A' + (i % alphabetLength)); } - return paramName; + return new string(characters, 0, totalCharacters); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj b/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj index 2ea01d4ebbe..c00ba8e6518 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj @@ -3,6 +3,7 @@ uap10.0.17763 Windows Community Toolkit Animations + true This library provides helpers and extensions on top of Windows Composition and XAML storyboards. It is part of the Windows Community Toolkit. diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Properties/SkipLocalsInitAttribute.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Properties/SkipLocalsInitAttribute.cs new file mode 100644 index 00000000000..4f0d98a5f82 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Properties/SkipLocalsInitAttribute.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.CompilerServices +{ + /// + /// Used to indicate to the compiler that the .locals init flag should not be set in method headers. + /// + /// Internal copy from the BCL attribute. + [AttributeUsage( + AttributeTargets.Module | + AttributeTargets.Class | + AttributeTargets.Struct | + AttributeTargets.Interface | + AttributeTargets.Constructor | + AttributeTargets.Method | + AttributeTargets.Property | + AttributeTargets.Event, + Inherited = false)] + internal sealed class SkipLocalsInitAttribute : Attribute + { + } +} \ No newline at end of file