mirror of
https://github.com/oliverbooth/X10D
synced 2024-11-09 22:55:42 +00:00
Merge pull request #70 from RealityProgrammer/main
Code fixup and optimization
This commit is contained in:
commit
f8243923df
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1 +1,2 @@
|
||||
ko_fi: oliverbooth
|
||||
custom: ['https://buymeacoffee.com/oliverbooth']
|
||||
|
103
X10D.Tests/src/Core/SpanTest.cs
Normal file
103
X10D.Tests/src/Core/SpanTest.cs
Normal file
@ -0,0 +1,103 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using X10D.Collections;
|
||||
using X10D.Core;
|
||||
|
||||
namespace X10D.Tests.Core;
|
||||
|
||||
[TestClass]
|
||||
public class SpanTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void Pack8Bit_Should_Pack_Correctly()
|
||||
{
|
||||
Span<bool> span = stackalloc bool[8] { true, true, false, false, true, true, false, false };
|
||||
Assert.AreEqual(0b00110011, span.PackByte());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Pack8Bit_Should_Pack_Correctly_Randomize()
|
||||
{
|
||||
var value = new Random().NextByte();
|
||||
|
||||
Span<bool> unpacks = stackalloc bool[8];
|
||||
|
||||
value.Unpack(unpacks);
|
||||
|
||||
Assert.AreEqual(value, unpacks.PackByte());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Pack16Bit_Should_Pack_Correctly()
|
||||
{
|
||||
ReadOnlySpan<bool> span = stackalloc bool[16] {
|
||||
false, false, true, false, true, false, true, true,
|
||||
true, false, true, true, false, true, false, false,
|
||||
};
|
||||
Assert.AreEqual(0b00101101_11010100, span.PackInt16());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Pack16Bit_Should_Pack_Correctly_Randomize()
|
||||
{
|
||||
var value = new Random().NextInt16();
|
||||
|
||||
Span<bool> unpacks = stackalloc bool[16];
|
||||
|
||||
value.Unpack(unpacks);
|
||||
|
||||
Assert.AreEqual(value, unpacks.PackInt16());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Pack32Bit_Should_Pack_Correctly()
|
||||
{
|
||||
ReadOnlySpan<bool> span = stackalloc bool[] {
|
||||
false, true, false, true, false, true, false, true,
|
||||
true, false, true, false, true, false, true, false,
|
||||
false, true, false, true, false, true, false, true,
|
||||
true, false, true, false, true, false, true, false,
|
||||
};
|
||||
Assert.AreEqual(0b01010101_10101010_01010101_10101010, span.PackInt32());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Pack32Bit_Should_Pack_Correctly_Randomize()
|
||||
{
|
||||
var value = new Random().Next(int.MinValue, int.MaxValue);
|
||||
|
||||
Span<bool> unpacks = stackalloc bool[32];
|
||||
|
||||
value.Unpack(unpacks);
|
||||
|
||||
Assert.AreEqual(value, unpacks.PackInt32());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Pack64Bit_Should_Pack_Correctly()
|
||||
{
|
||||
ReadOnlySpan<bool> span = stackalloc bool[] {
|
||||
true, false, true, false, false, true, false, true,
|
||||
false, false, true, true, false, true, false, false,
|
||||
true, true, true, false, true, false, false, true,
|
||||
false, true, false, false, true, false, false, false,
|
||||
false, true, true, false, true, false, true, true,
|
||||
true, false, false, true, false, true, true, false,
|
||||
false, true, true, false, true, false, true, true,
|
||||
true, false, true, false, true, false, true, false,
|
||||
};
|
||||
Assert.AreEqual(0b01010101_11010110_01101001_11010110_00010010_10010111_00101100_10100101, span.PackInt64());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Pack64Bit_Should_Pack_Correctly_Randomize()
|
||||
{
|
||||
var rand = new Random();
|
||||
long value = ((long)rand.Next() << 32) | (long)rand.Next();
|
||||
|
||||
Span<bool> unpacks = stackalloc bool[64];
|
||||
|
||||
value.Unpack(unpacks);
|
||||
|
||||
Assert.AreEqual(value, unpacks.PackInt64());
|
||||
}
|
||||
}
|
@ -45,6 +45,38 @@ public class RuneTests
|
||||
Assert.AreEqual("a", repeated);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RepeatCodepoint_0000_007F_ShouldCorrect()
|
||||
{
|
||||
string repeated = new Rune(69).Repeat(16);
|
||||
Assert.AreEqual(16, repeated.Length);
|
||||
Assert.AreEqual("EEEEEEEEEEEEEEEE", repeated);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RepeatCodepoint_0080_07FF_ShouldCorrect()
|
||||
{
|
||||
string repeated = new Rune(192).Repeat(8);
|
||||
Assert.AreEqual(8, repeated.Length);
|
||||
Assert.AreEqual("ÀÀÀÀÀÀÀÀ", repeated);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RepeatCodepoint_0800_FFFF_ShouldCorrect()
|
||||
{
|
||||
string repeated = new Rune(0x0800).Repeat(5);
|
||||
Assert.AreEqual(5, repeated.Length);
|
||||
Assert.AreEqual("ࠀࠀࠀࠀࠀ", repeated);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RepeatCodepointBeyondU10000ShouldCorrect()
|
||||
{
|
||||
string repeated = new Rune('\uD800', '\uDC00').Repeat(6);
|
||||
Assert.AreEqual(12, repeated.Length);
|
||||
Assert.AreEqual("𐀀𐀀𐀀𐀀𐀀𐀀", repeated);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RepeatZeroCountShouldBeEmpty()
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace X10D.Unity.Numerics;
|
||||
@ -18,7 +19,7 @@ public static class QuaternionExtensions
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static System.Numerics.Quaternion ToSystemQuaternion(this Quaternion quaternion)
|
||||
{
|
||||
return new System.Numerics.Quaternion(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
|
||||
return UnsafeUtility.As<Quaternion, System.Numerics.Quaternion>(ref quaternion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -30,6 +31,6 @@ public static class QuaternionExtensions
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion ToUnityQuaternion(this System.Numerics.Quaternion quaternion)
|
||||
{
|
||||
return new Quaternion(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
|
||||
return UnsafeUtility.As<System.Numerics.Quaternion, Quaternion>(ref quaternion);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
using X10D.Drawing;
|
||||
using X10D.Math;
|
||||
@ -135,7 +136,7 @@ public static class Vector2Extensions
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static System.Numerics.Vector2 ToSystemVector(this Vector2 vector)
|
||||
{
|
||||
return new System.Numerics.Vector2(vector.x, vector.y);
|
||||
return UnsafeUtility.As<Vector2, System.Numerics.Vector2>(ref vector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -147,7 +148,7 @@ public static class Vector2Extensions
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector2 ToUnityVector(this System.Numerics.Vector2 vector)
|
||||
{
|
||||
return new Vector2(vector.X, vector.Y);
|
||||
return UnsafeUtility.As<System.Numerics.Vector2, Vector2>(ref vector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
using X10D.Math;
|
||||
|
||||
@ -61,7 +62,7 @@ public static class Vector3Extensions
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static System.Numerics.Vector3 ToSystemVector(this Vector3 vector)
|
||||
{
|
||||
return new System.Numerics.Vector3(vector.x, vector.y, vector.z);
|
||||
return UnsafeUtility.As<Vector3, System.Numerics.Vector3>(ref vector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -73,7 +74,7 @@ public static class Vector3Extensions
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector3 ToUnityVector(this System.Numerics.Vector3 vector)
|
||||
{
|
||||
return new Vector3(vector.X, vector.Y, vector.Z);
|
||||
return UnsafeUtility.As<System.Numerics.Vector3, Vector3>(ref vector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
using X10D.Math;
|
||||
|
||||
@ -64,7 +65,8 @@ public static class Vector4Extensions
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static System.Numerics.Vector4 ToSystemVector(this Vector4 vector)
|
||||
{
|
||||
return new System.Numerics.Vector4(vector.x, vector.y, vector.z, vector.w);
|
||||
return UnsafeUtility.As<Vector4, System.Numerics.Vector4>(ref vector);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -76,7 +78,7 @@ public static class Vector4Extensions
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector4 ToUnityVector(this System.Numerics.Vector4 vector)
|
||||
{
|
||||
return new Vector4(vector.X, vector.Y, vector.Z, vector.W);
|
||||
return UnsafeUtility.As<System.Numerics.Vector4, Vector4>(ref vector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
2
X10D/Resource.Designer.cs
generated
2
X10D/Resource.Designer.cs
generated
@ -19,7 +19,7 @@ namespace X10D {
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resource {
|
||||
|
@ -58,14 +58,8 @@ public static class ArrayExtensions
|
||||
}
|
||||
#endif
|
||||
|
||||
int index = range.Start.Value;
|
||||
int end = range.End.Value;
|
||||
if (range.End.IsFromEnd)
|
||||
{
|
||||
end = array.Length - end;
|
||||
}
|
||||
|
||||
array.Clear(index, end - index);
|
||||
(int offset, int length) = range.GetOffsetAndLength(array.Length);
|
||||
array.Clear(offset, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,10 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
|
||||
namespace X10D.Collections;
|
||||
|
||||
/// <summary>
|
||||
@ -17,9 +22,9 @@ public static class ByteExtensions
|
||||
[Pure]
|
||||
public static bool[] Unpack(this byte value)
|
||||
{
|
||||
Span<bool> buffer = stackalloc bool[Size];
|
||||
var buffer = new bool[Size];
|
||||
value.Unpack(buffer);
|
||||
return buffer.ToArray();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -35,9 +40,43 @@ public static class ByteExtensions
|
||||
throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination));
|
||||
}
|
||||
|
||||
for (var index = 0; index < Size; index++)
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
if (Ssse3.IsSupported)
|
||||
{
|
||||
destination[index] = (value & (1 << index)) != 0;
|
||||
Ssse3Implementation(value, destination);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
FallbackImplementation(value, destination);
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
unsafe static void Ssse3Implementation(byte value, Span<bool> destination)
|
||||
{
|
||||
fixed (bool* pDestination = destination)
|
||||
{
|
||||
var mask2 = Vector128.Create(
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
|
||||
);
|
||||
var mask1 = Vector128.Create((byte)0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1);
|
||||
|
||||
var vec = Vector128.Create(value).AsByte();
|
||||
var shuffle = Ssse3.Shuffle(vec, mask1);
|
||||
var and = Sse2.AndNot(shuffle, mask2);
|
||||
var cmp = Sse2.CompareEqual(and, Vector128<byte>.Zero);
|
||||
var correctness = Sse2.And(cmp, Vector128.Create((byte)0x01));
|
||||
|
||||
Sse2.StoreScalar((long*)pDestination, correctness.AsInt64());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
static void FallbackImplementation(byte value, Span<bool> destination)
|
||||
{
|
||||
for (var index = 0; index < Size; index++)
|
||||
{
|
||||
destination[index] = (value & (1 << index)) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Web;
|
||||
|
||||
namespace X10D.Collections;
|
||||
@ -8,6 +10,77 @@ namespace X10D.Collections;
|
||||
/// </summary>
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a key/value pair to the <see cref="Dictionary{TKey,TValue}" /> if the key does not already exist, or updates a
|
||||
/// key/value pair in the <see cref="Dictionary{TKey,TValue}" /> by using the specified function if the key already
|
||||
/// exists.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The dictionary to update.</param>
|
||||
/// <param name="key">The key to be added or whose value should be updated.</param>
|
||||
/// <param name="addValue">The value to be added for an absent key.</param>
|
||||
/// <param name="updateValueFactory">
|
||||
/// The function used to generate a new value for an existing key based on the key's existing value.
|
||||
/// </param>
|
||||
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
|
||||
/// <returns>
|
||||
/// The new value for the key. This will be either be <paramref name="addValue" /> (if the key was absent) or the result
|
||||
/// of <paramref name="updateValueFactory" /> (if the key was present).
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="dictionary" /> is <see langword="null" />.</para>
|
||||
/// -or-
|
||||
/// <para><paramref name="updateValueFactory" /> is <see langword="null" />.</para>
|
||||
/// </exception>
|
||||
public static TValue AddOrUpdate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue addValue,
|
||||
Func<TKey, TValue, TValue> updateValueFactory)
|
||||
where TKey : notnull
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
ArgumentNullException.ThrowIfNull(dictionary);
|
||||
#else
|
||||
if (dictionary is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dictionary));
|
||||
}
|
||||
#endif
|
||||
#if NET6_0_OR_GREATER
|
||||
ArgumentNullException.ThrowIfNull(updateValueFactory);
|
||||
#else
|
||||
if (updateValueFactory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(updateValueFactory));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
value = updateValueFactory(key, value!);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = addValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
#else
|
||||
if (dictionary.TryGetValue(key, out TValue? old))
|
||||
{
|
||||
var updated = updateValueFactory(key, old);
|
||||
dictionary[key] = updated;
|
||||
|
||||
return updated;
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary.Add(key, addValue);
|
||||
return addValue;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value pair to the <see cref="IDictionary{TKey,TValue}" /> if the key does not already exist, or updates a
|
||||
/// key/value pair in the <see cref="IDictionary{TKey,TValue}" /> by using the specified function if the key already
|
||||
@ -51,16 +124,101 @@ public static class DictionaryExtensions
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dictionary.ContainsKey(key))
|
||||
if (dictionary.TryGetValue(key, out TValue? old))
|
||||
{
|
||||
dictionary[key] = updateValueFactory(key, dictionary[key]);
|
||||
var updated = updateValueFactory(key, old);
|
||||
dictionary[key] = updated;
|
||||
|
||||
return updated;
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary.Add(key, addValue);
|
||||
return addValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the specified functions to add a key/value pair to the <see cref="Dictionary{TKey,TValue}" /> if the key does
|
||||
/// not already exist, or to update a key/value pair in the <see cref="Dictionary{TKey,TValue}" /> if the key already
|
||||
/// exists.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The dictionary to update.</param>
|
||||
/// <param name="key">The key to be added or whose value should be updated.</param>
|
||||
/// <param name="addValueFactory">The function used to generate a value for an absent key.</param>
|
||||
/// <param name="updateValueFactory">
|
||||
/// The function used to generate a new value for an existing key based on the key's existing value.
|
||||
/// </param>
|
||||
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
|
||||
/// <returns>
|
||||
/// The new value for the key. This will be either be the result of <paramref name="addValueFactory "/> (if the key was
|
||||
/// absent) or the result of <paramref name="updateValueFactory" /> (if the key was present).
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="dictionary" /> is <see langword="null" />.</para>
|
||||
/// -or-
|
||||
/// <para><paramref name="addValueFactory" /> is <see langword="null" />.</para>
|
||||
/// -or-
|
||||
/// <para><paramref name="updateValueFactory" /> is <see langword="null" />.</para>
|
||||
/// </exception>
|
||||
public static TValue AddOrUpdate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key,
|
||||
Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
|
||||
where TKey : notnull
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
ArgumentNullException.ThrowIfNull(dictionary);
|
||||
#else
|
||||
if (dictionary is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dictionary));
|
||||
}
|
||||
#endif
|
||||
#if NET6_0_OR_GREATER
|
||||
ArgumentNullException.ThrowIfNull(addValueFactory);
|
||||
#else
|
||||
if (addValueFactory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(addValueFactory));
|
||||
}
|
||||
#endif
|
||||
#if NET6_0_OR_GREATER
|
||||
ArgumentNullException.ThrowIfNull(updateValueFactory);
|
||||
#else
|
||||
if (updateValueFactory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(updateValueFactory));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
ref TValue? value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
value = updateValueFactory(key, value!);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = addValueFactory(key);
|
||||
}
|
||||
|
||||
return dictionary[key];
|
||||
return value;
|
||||
#else
|
||||
if (dictionary.TryGetValue(key, out TValue? old))
|
||||
{
|
||||
var updated = updateValueFactory(key, old);
|
||||
dictionary[key] = updated;
|
||||
|
||||
return updated;
|
||||
}
|
||||
else
|
||||
{
|
||||
var add = addValueFactory(key);
|
||||
dictionary.Add(key, add);
|
||||
|
||||
return add;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -116,16 +274,109 @@ public static class DictionaryExtensions
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dictionary.ContainsKey(key))
|
||||
if (dictionary.TryGetValue(key, out TValue? old))
|
||||
{
|
||||
dictionary[key] = updateValueFactory(key, dictionary[key]);
|
||||
var updated = updateValueFactory(key, old);
|
||||
dictionary[key] = updated;
|
||||
|
||||
return updated;
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary.Add(key, addValueFactory(key));
|
||||
var add = addValueFactory(key);
|
||||
dictionary.Add(key, add);
|
||||
|
||||
return add;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the specified functions and argument to add a key/value pair to the <see cref="Dictionary{TKey,TValue}" /> if
|
||||
/// the key does not already exist, or to update a key/value pair in the <see cref="Dictionary{TKey,TValue}" /> if th
|
||||
/// key already exists.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The dictionary to update.</param>
|
||||
/// <param name="key">The key to be added or whose value should be updated.</param>
|
||||
/// <param name="addValueFactory">The function used to generate a value for an absent key.</param>
|
||||
/// <param name="updateValueFactory">
|
||||
/// The function used to generate a new value for an existing key based on the key's existing value.
|
||||
/// </param>
|
||||
/// <param name="factoryArgument">
|
||||
/// An argument to pass into <paramref name="addValueFactory" /> and <paramref name="updateValueFactory" />.
|
||||
/// </param>
|
||||
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
|
||||
/// <typeparam name="TArg">
|
||||
/// The type of an argument to pass into <paramref name="addValueFactory" /> and <paramref name="updateValueFactory" />.
|
||||
/// </typeparam>
|
||||
/// <returns>
|
||||
/// The new value for the key. This will be either be the result of <paramref name="addValueFactory "/> (if the key was
|
||||
/// absent) or the result of <paramref name="updateValueFactory" /> (if the key was present).
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <para><paramref name="dictionary" /> is <see langword="null" />.</para>
|
||||
/// -or-
|
||||
/// <para><paramref name="addValueFactory" /> is <see langword="null" />.</para>
|
||||
/// -or-
|
||||
/// <para><paramref name="updateValueFactory" /> is <see langword="null" />.</para>
|
||||
/// </exception>
|
||||
public static TValue AddOrUpdate<TKey, TValue, TArg>(this Dictionary<TKey, TValue> dictionary, TKey key,
|
||||
Func<TKey, TArg, TValue> addValueFactory, Func<TKey, TValue, TArg, TValue> updateValueFactory, TArg factoryArgument)
|
||||
where TKey : notnull
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
ArgumentNullException.ThrowIfNull(dictionary);
|
||||
#else
|
||||
if (dictionary is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dictionary));
|
||||
}
|
||||
#endif
|
||||
#if NET6_0_OR_GREATER
|
||||
ArgumentNullException.ThrowIfNull(addValueFactory);
|
||||
#else
|
||||
if (addValueFactory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(addValueFactory));
|
||||
}
|
||||
#endif
|
||||
#if NET6_0_OR_GREATER
|
||||
ArgumentNullException.ThrowIfNull(updateValueFactory);
|
||||
#else
|
||||
if (updateValueFactory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(updateValueFactory));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
ref TValue? value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool exists);
|
||||
if (exists)
|
||||
{
|
||||
value = updateValueFactory(key, value!, factoryArgument);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = addValueFactory(key, factoryArgument);
|
||||
}
|
||||
|
||||
return dictionary[key];
|
||||
return value;
|
||||
#else
|
||||
if (dictionary.TryGetValue(key, out TValue? old))
|
||||
{
|
||||
var updated = updateValueFactory(key, old, factoryArgument);
|
||||
dictionary[key] = updated;
|
||||
|
||||
return updated;
|
||||
}
|
||||
else
|
||||
{
|
||||
var add = addValueFactory(key, factoryArgument);
|
||||
dictionary.Add(key, add);
|
||||
|
||||
return add;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -187,16 +438,20 @@ public static class DictionaryExtensions
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dictionary.ContainsKey(key))
|
||||
if (dictionary.TryGetValue(key, out TValue? old))
|
||||
{
|
||||
dictionary[key] = updateValueFactory(key, dictionary[key], factoryArgument);
|
||||
var updated = updateValueFactory(key, old, factoryArgument);
|
||||
dictionary[key] = updated;
|
||||
|
||||
return updated;
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary.Add(key, addValueFactory(key, factoryArgument));
|
||||
}
|
||||
var add = addValueFactory(key, factoryArgument);
|
||||
dictionary.Add(key, add);
|
||||
|
||||
return dictionary[key];
|
||||
return add;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,10 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
|
||||
namespace X10D.Collections;
|
||||
|
||||
/// <summary>
|
||||
@ -17,9 +22,9 @@ public static class Int16Extensions
|
||||
[Pure]
|
||||
public static bool[] Unpack(this short value)
|
||||
{
|
||||
Span<bool> buffer = stackalloc bool[Size];
|
||||
value.Unpack(buffer);
|
||||
return buffer.ToArray();
|
||||
var ret = new bool[Size];
|
||||
value.Unpack(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -35,9 +40,45 @@ public static class Int16Extensions
|
||||
throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination));
|
||||
}
|
||||
|
||||
for (var index = 0; index < Size; index++)
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
if (Ssse3.IsSupported)
|
||||
{
|
||||
destination[index] = (value & (1 << index)) != 0;
|
||||
Ssse3Implementation(value, destination);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
FallbackImplementation(value, destination);
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
unsafe static void Ssse3Implementation(short value, Span<bool> destination)
|
||||
{
|
||||
fixed (bool* pDestination = destination)
|
||||
{
|
||||
var mask2 = Vector128.Create(
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
|
||||
);
|
||||
var mask1Lo = Vector128.Create((byte)0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1);
|
||||
|
||||
var one = Vector128.Create((byte)0x01);
|
||||
|
||||
var vec = Vector128.Create(value).AsByte();
|
||||
var shuffle = Ssse3.Shuffle(vec, mask1Lo);
|
||||
var and = Sse2.AndNot(shuffle, mask2);
|
||||
var cmp = Sse2.CompareEqual(and, Vector128<byte>.Zero);
|
||||
var correctness = Sse2.And(cmp, one);
|
||||
|
||||
Sse2.Store((byte*)pDestination, correctness);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
static void FallbackImplementation(short value, Span<bool> destination)
|
||||
{
|
||||
for (var index = 0; index < Size; index++)
|
||||
{
|
||||
destination[index] = (value & (1 << index)) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
|
||||
namespace X10D.Collections;
|
||||
|
||||
/// <summary>
|
||||
@ -17,9 +22,9 @@ public static class Int32Extensions
|
||||
[Pure]
|
||||
public static bool[] Unpack(this int value)
|
||||
{
|
||||
Span<bool> buffer = stackalloc bool[Size];
|
||||
value.Unpack(buffer);
|
||||
return buffer.ToArray();
|
||||
var ret = new bool[Size];
|
||||
value.Unpack(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -35,9 +40,87 @@ public static class Int32Extensions
|
||||
throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination));
|
||||
}
|
||||
|
||||
for (var index = 0; index < Size; index++)
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
// TODO: AdvSimd support.
|
||||
|
||||
// https://stackoverflow.com/questions/24225786/fastest-way-to-unpack-32-bits-to-a-32-byte-simd-vector
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
destination[index] = (value & (1 << index)) != 0;
|
||||
Avx2Implementation(value, destination);
|
||||
return;
|
||||
}
|
||||
if (Ssse3.IsSupported)
|
||||
{
|
||||
Ssse3Implementation(value, destination);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
FallbackImplementation(value, destination);
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
unsafe static void Avx2Implementation(int value, Span<bool> destination)
|
||||
{
|
||||
fixed (bool* pDestination = destination)
|
||||
{
|
||||
var mask1 = Vector256.Create(
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03
|
||||
).AsByte();
|
||||
var mask2 = Vector256.Create(
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
|
||||
);
|
||||
|
||||
var vec = Vector256.Create(value).AsByte();
|
||||
var shuffle = Avx2.Shuffle(vec, mask1);
|
||||
var and = Avx2.AndNot(shuffle, mask2);
|
||||
var cmp = Avx2.CompareEqual(and, Vector256<byte>.Zero);
|
||||
var correctness = Avx2.And(cmp, Vector256.Create((byte)0x01));
|
||||
|
||||
Avx.Store((byte*)pDestination, correctness);
|
||||
}
|
||||
}
|
||||
unsafe static void Ssse3Implementation(int value, Span<bool> destination)
|
||||
{
|
||||
fixed (bool* pDestination = destination)
|
||||
{
|
||||
var mask2 = Vector128.Create(
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
|
||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
|
||||
);
|
||||
var mask1Lo = Vector128.Create((byte)0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1);
|
||||
var mask1Hi = Vector128.Create((byte)2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3);
|
||||
|
||||
var one = Vector128.Create((byte)0x01);
|
||||
|
||||
var vec = Vector128.Create(value).AsByte();
|
||||
var shuffle = Ssse3.Shuffle(vec, mask1Lo);
|
||||
var and = Sse2.AndNot(shuffle, mask2);
|
||||
var cmp = Sse2.CompareEqual(and, Vector128<byte>.Zero);
|
||||
var correctness = Sse2.And(cmp, one);
|
||||
|
||||
Sse2.Store((byte*)pDestination, correctness);
|
||||
|
||||
shuffle = Ssse3.Shuffle(vec, mask1Hi);
|
||||
and = Sse2.AndNot(shuffle, mask2);
|
||||
cmp = Sse2.CompareEqual(and, Vector128<byte>.Zero);
|
||||
correctness = Sse2.And(cmp, one);
|
||||
|
||||
Sse2.Store((byte*)pDestination + 16, correctness);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
static void FallbackImplementation(int value, Span<bool> destination)
|
||||
{
|
||||
for (var index = 0; index < Size; index++)
|
||||
{
|
||||
destination[index] = (value & (1 << index)) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ public static class Int64Extensions
|
||||
[Pure]
|
||||
public static bool[] Unpack(this long value)
|
||||
{
|
||||
Span<bool> buffer = stackalloc bool[Size];
|
||||
value.Unpack(buffer);
|
||||
return buffer.ToArray();
|
||||
var ret = new bool[Size];
|
||||
value.Unpack(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
166
X10D/src/Core/IntrinsicExtensions.cs
Normal file
166
X10D/src/Core/IntrinsicExtensions.cs
Normal file
@ -0,0 +1,166 @@
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace X10D.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for SIMD vectors, namely <see cref="Vector64{T}"/>, <see cref="Vector128{T}"/> and
|
||||
/// <see cref="Vector256{T}"/>.
|
||||
/// </summary>
|
||||
public static class IntrinsicExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Correcting <see cref="Vector64{T}"/> of <see langword="byte"/> into 0 and 1 depend on their boolean truthiness.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// for (int i = 0; i < 8; i++) {
|
||||
/// dest[i] = vector[i] == 0 ? 0 : 1;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="vector">Vector of byte to correct.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector64{T}"/> of <see langword="byte"/> which remapped back to 0 and 1 based on boolean truthiness.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static Vector64<byte> CorrectBoolean(this Vector64<byte> vector)
|
||||
{
|
||||
// TODO: AdvSimd implementation.
|
||||
// TODO: WasmSimd implementation. (?)
|
||||
|
||||
var output = IntrinsicUtility.GetUninitializedVector64<byte>();
|
||||
|
||||
for (int i = 0; i < Vector64<byte>.Count; i++)
|
||||
{
|
||||
ref var writeElement = ref Unsafe.Add(ref Unsafe.As<Vector64<byte>, byte>(ref output), i);
|
||||
#if NET7_0_OR_GREATER
|
||||
writeElement = vector[i] == 0 ? (byte)0 : (byte)1;
|
||||
#else
|
||||
var element = Unsafe.Add(ref Unsafe.As<Vector64<byte>, byte>(ref vector), i);
|
||||
writeElement = element == 0 ? (byte)0 : (byte)1;
|
||||
#endif
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Correcting <see cref="Vector128{T}"/> of <see langword="byte"/> into 0 and 1 depend on their boolean truthiness.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// for (int i = 0; i < 16; i++) {
|
||||
/// dest[i] = vector[i] == 0 ? 0 : 1;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="vector">Vector of byte to correct.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector128{T}"/> of <see langword="byte"/> which remapped back to 0 and 1 based on boolean truthiness.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static Vector128<byte> CorrectBoolean(this Vector128<byte> vector)
|
||||
{
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
var cmp = Sse2.CompareEqual(vector, Vector128<byte>.Zero);
|
||||
var result = Sse2.AndNot(cmp, Vector128.Create((byte)1));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: AdvSimd implementation.
|
||||
// TODO: WasmSimd implementation.
|
||||
|
||||
var output = IntrinsicUtility.GetUninitializedVector128<byte>();
|
||||
|
||||
for (int i = 0; i < Vector128<byte>.Count; i++)
|
||||
{
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<byte>, byte>(ref output), i) =
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<byte>, byte>(ref vector), i) == 0 ? (byte)0 : (byte)1;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Correcting <see cref="Vector256{T}"/> of <see langword="byte"/> into 0 and 1 depend on their boolean truthiness.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// for (int i = 0; i < 32; i++) {
|
||||
/// dest[i] = vector[i] == 0 ? 0 : 1;
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="vector">Vector of byte to correct.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector256{T}"/> of <see langword="byte"/> which remapped back to 0 and 1 based on boolean truthiness.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static Vector256<byte> CorrectBoolean(this Vector256<byte> vector)
|
||||
{
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
var cmp = Avx2.CompareEqual(vector, Vector256<byte>.Zero);
|
||||
var result = Avx2.AndNot(cmp, Vector256.Create((byte)1));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var output = IntrinsicUtility.GetUninitializedVector256<byte>();
|
||||
|
||||
for (int i = 0; i < Vector256<byte>.Count; i++)
|
||||
{
|
||||
Unsafe.Add(ref Unsafe.As<Vector256<byte>, byte>(ref output), i) =
|
||||
Unsafe.Add(ref Unsafe.As<Vector256<byte>, byte>(ref vector), i) == 0 ? (byte)0 : (byte)1;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Reverse position of 2 64-bit unsigned integer.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// dest[1] = vector[0];
|
||||
/// dest[0] = vector[1];
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="vector">Input vector.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector128{T}"/> of <see langword="ulong"/> with elements the same as input vector except their positions
|
||||
/// (or indices) are reversed.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[CLSCompliant(false)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static Vector128<ulong> ReverseElements(this Vector128<ulong> vector)
|
||||
{
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
return Sse2.Shuffle(vector.AsDouble(), vector.AsDouble(), 0b01).AsUInt64();
|
||||
}
|
||||
|
||||
Vector128<ulong> output = IntrinsicUtility.GetUninitializedVector128<ulong>();
|
||||
|
||||
Unsafe.As<Vector128<ulong>, ulong>(ref output) = Unsafe.Add(ref Unsafe.As<Vector128<ulong>, ulong>(ref vector), 1);
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<ulong>, ulong>(ref output), 1) = Unsafe.As<Vector128<ulong>, ulong>(ref vector);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
#endif
|
306
X10D/src/Core/IntrinsicUtility.cs
Normal file
306
X10D/src/Core/IntrinsicUtility.cs
Normal file
@ -0,0 +1,306 @@
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace X10D.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides utility methods for SIMD vector that is currently missing on common hardware instruction set.
|
||||
/// </summary>
|
||||
public static class IntrinsicUtility
|
||||
{
|
||||
// NOTE:
|
||||
// ANY METHOD THAT OPERATE ON ANYTHING THAT ISN'T FLOAT IS NOT SSE COMPATIBLE, MUST BE SSE2 AND BEYONDS
|
||||
// FOR API CONSISTENCY.
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Multiply packed 64-bit unsigned integer elements in a and b and truncate the results to 64-bit integer.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// dest[0] = lhs[0] * rhs[0];
|
||||
/// dest[1] = lhs[1] * rhs[1];
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="lhs">Left vector.</param>
|
||||
/// <param name="rhs">Right vector.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector128{T}"/> of <see langword="ulong"/> whose elements is 64-bit truncated product of lhs and rhs.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[CLSCompliant(false)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static Vector128<ulong> Multiply(Vector128<ulong> lhs, Vector128<ulong> rhs)
|
||||
{
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
// https://stackoverflow.com/questions/17863411/sse-multiplication-of-2-64-bit-integers
|
||||
|
||||
Vector128<ulong> ac = Sse2.Multiply(lhs.AsUInt32(), rhs.AsUInt32());
|
||||
Vector128<uint> b = Sse2.ShiftRightLogical(lhs, 32).AsUInt32();
|
||||
Vector128<ulong> bc = Sse2.Multiply(b, rhs.AsUInt32());
|
||||
Vector128<uint> d = Sse2.ShiftRightLogical(rhs, 32).AsUInt32();
|
||||
Vector128<ulong> ad = Sse2.Multiply(lhs.AsUInt32(), d);
|
||||
Vector128<ulong> high = Sse2.Add(bc, ad);
|
||||
high = Sse2.ShiftLeftLogical(high, 32);
|
||||
|
||||
return Sse2.Add(high, ac);
|
||||
}
|
||||
|
||||
// TODO: AdvSimd implementation.
|
||||
// TODO: WasmSimd implementation.
|
||||
|
||||
var output = GetUninitializedVector128<ulong>();
|
||||
|
||||
Unsafe.As<Vector128<ulong>, ulong>(ref output) =
|
||||
Unsafe.As<Vector128<ulong>, ulong>(ref lhs) * Unsafe.As<Vector128<ulong>, ulong>(ref rhs);
|
||||
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<ulong>, ulong>(ref output), 1) =
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<ulong>, ulong>(ref lhs), 1) *
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<ulong>, ulong>(ref rhs), 1);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Multiply packed 64-bit unsigned integer elements in a and b and truncate the results to 64-bit integer.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// dest[0] = lhs[0] * rhs[0];
|
||||
/// dest[1] = lhs[1] * rhs[1];
|
||||
/// dest[2] = lhs[2] * rhs[2];
|
||||
/// dest[3] = lhs[3] * rhs[3];
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="lhs">Left vector.</param>
|
||||
/// <param name="rhs">Right vector.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector256{T}"/> of <see langword="ulong"/> whose elements is 64-bit truncated product of lhs and rhs.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[CLSCompliant(false)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static Vector256<ulong> Multiply(Vector256<ulong> lhs, Vector256<ulong> rhs)
|
||||
{
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
// https://stackoverflow.com/questions/17863411/sse-multiplication-of-2-64-bit-integers
|
||||
|
||||
Vector256<ulong> ac = Avx2.Multiply(lhs.AsUInt32(), rhs.AsUInt32());
|
||||
Vector256<uint> b = Avx2.ShiftRightLogical(lhs, 32).AsUInt32();
|
||||
Vector256<ulong> bc = Avx2.Multiply(b, rhs.AsUInt32());
|
||||
Vector256<uint> d = Avx2.ShiftRightLogical(rhs, 32).AsUInt32();
|
||||
Vector256<ulong> ad = Avx2.Multiply(lhs.AsUInt32(), d);
|
||||
Vector256<ulong> high = Avx2.Add(bc, ad);
|
||||
high = Avx2.ShiftLeftLogical(high, 32);
|
||||
|
||||
return Avx2.Add(high, ac);
|
||||
}
|
||||
|
||||
var output = GetUninitializedVector256<ulong>();
|
||||
|
||||
for (int i = 0; i < Vector256<ulong>.Count; i++)
|
||||
{
|
||||
Unsafe.Add(ref Unsafe.As<Vector256<ulong>, ulong>(ref output), i) =
|
||||
Unsafe.Add(ref Unsafe.As<Vector256<ulong>, ulong>(ref lhs), i) *
|
||||
Unsafe.Add(ref Unsafe.As<Vector256<ulong>, ulong>(ref rhs), i);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Multiply packed 64-bit signed integer elements in a and b and truncate the results to 64-bit integer.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// dest[0] = lhs[0] * rhs[0];
|
||||
/// dest[1] = lhs[1] * rhs[1];
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="lhs">Left vector.</param>
|
||||
/// <param name="rhs">Right vector.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector128{T}"/> of <see langword="long"/> whose elements is 64-bit truncated product of lhs and rhs.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static Vector128<long> Multiply(Vector128<long> lhs, Vector128<long> rhs)
|
||||
{
|
||||
return Multiply(lhs.AsUInt64(), rhs.AsUInt64()).AsInt64();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Multiply packed 64-bit signed integer elements in a and b and truncate the results to 64-bit integer.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// dest[0] = lhs[0] * rhs[0];
|
||||
/// dest[1] = lhs[1] * rhs[1];
|
||||
/// dest[2] = lhs[2] * rhs[2];
|
||||
/// dest[3] = lhs[3] * rhs[3];
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="lhs">Left vector.</param>
|
||||
/// <param name="rhs">Right vector.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector256{T}"/> of <see langword="ulong"/> whose elements is 64-bit truncated product of lhs and rhs.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static Vector256<long> Multiply(Vector256<long> lhs, Vector256<long> rhs)
|
||||
{
|
||||
return Multiply(lhs.AsUInt64(), rhs.AsUInt64()).AsInt64();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Horizontally apply OR operation on adjacent pairs of single-precision (32-bit) floating-point elements in lhs and
|
||||
/// rhs.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// dest[0] = lhs[0] | lhs[1];
|
||||
/// dest[1] = lhs[2] | lhs[3];
|
||||
/// dest[2] = rhs[0] | rhs[1];
|
||||
/// dest[3] = rhs[2] | rhs[3];
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="lhs">Left vector.</param>
|
||||
/// <param name="rhs">Right vector.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector128{T}"/> of <see langword="float"/> with all elements is result of OR operation on adjacent pairs of
|
||||
/// elements in lhs and rhs.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static Vector128<float> HorizontalOr(Vector128<float> lhs, Vector128<float> rhs)
|
||||
{
|
||||
if (Sse.IsSupported)
|
||||
{
|
||||
var s1 = Sse.Shuffle(lhs, rhs, 0b10_00_10_00); // s1 = { lhs[0] ; lhs[2] ; rhs[0] ; rhs[2] }
|
||||
var s2 = Sse.Shuffle(lhs, rhs, 0b11_01_11_01); // s2 = { lhs[1] ; lhs[3] ; rhs[1] ; rhs[3] }
|
||||
|
||||
return Sse.Or(s1, s2);
|
||||
}
|
||||
|
||||
// TODO: AdvSimd implementation.
|
||||
// TODO: WasmSimd implementation. (?)
|
||||
|
||||
Vector128<float> output = GetUninitializedVector128<float>();
|
||||
|
||||
Unsafe.As<Vector128<float>, uint>(ref output) =
|
||||
Unsafe.As<Vector128<float>, uint>(ref lhs) |
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<float>, uint>(ref lhs), 1);
|
||||
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<float>, uint>(ref output), 1) =
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<float>, uint>(ref lhs), 2) |
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<float>, uint>(ref lhs), 3);
|
||||
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<float>, uint>(ref output), 2) =
|
||||
Unsafe.As<Vector128<float>, uint>(ref rhs) |
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<float>, uint>(ref rhs), 1);
|
||||
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<float>, uint>(ref output), 3) =
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<float>, uint>(ref rhs), 2) |
|
||||
Unsafe.Add(ref Unsafe.As<Vector128<float>, uint>(ref rhs), 3);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Horizontally apply OR operation on adjacent pairs of 32-bit integer elements in lhs and rhs.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// dest[0] = lhs[0] | lhs[1];
|
||||
/// dest[1] = lhs[2] | lhs[3];
|
||||
/// dest[2] = rhs[0] | rhs[1];
|
||||
/// dest[3] = rhs[2] | rhs[3];
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="lhs">Left vector.</param>
|
||||
/// <param name="rhs">Right vector.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector128{T}"/> of <see langword="int"/> with all elements is result of OR operation on adjacent pairs of
|
||||
/// elements in lhs and rhs.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static Vector128<int> HorizontalOr(Vector128<int> lhs, Vector128<int> rhs)
|
||||
{
|
||||
return HorizontalOr(lhs.AsSingle(), rhs.AsSingle()).AsInt32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Horizontally apply OR operation on adjacent pairs of 32-bit unsigned integer elements in lhs and rhs.
|
||||
/// </para>
|
||||
/// Operation:<br/>
|
||||
/// <code>
|
||||
/// dest[0] = lhs[0] | lhs[1];
|
||||
/// dest[1] = lhs[2] | lhs[3];
|
||||
/// dest[2] = rhs[0] | rhs[1];
|
||||
/// dest[3] = rhs[2] | rhs[3];
|
||||
/// </code>
|
||||
/// </summary>
|
||||
/// <param name="lhs">Left vector.</param>
|
||||
/// <param name="rhs">Right vector.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Vector128{T}"/> of <see langword="uint"/> with all elements is result of OR operation on adjacent pairs of
|
||||
/// elements in lhs and rhs.
|
||||
/// </returns>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
[CLSCompliant(false)]
|
||||
public static Vector128<uint> HorizontalOr(Vector128<uint> lhs, Vector128<uint> rhs)
|
||||
{
|
||||
return HorizontalOr(lhs.AsSingle(), rhs.AsSingle()).AsUInt32();
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static Vector64<T> GetUninitializedVector64<T>() where T : struct
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
Unsafe.SkipInit(out Vector64<T> output);
|
||||
return output;
|
||||
#else
|
||||
return default;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static Vector128<T> GetUninitializedVector128<T>() where T : struct
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
Unsafe.SkipInit(out Vector128<T> output);
|
||||
return output;
|
||||
#else
|
||||
return default;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static Vector256<T> GetUninitializedVector256<T>() where T : struct
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
Unsafe.SkipInit(out Vector256<T> output);
|
||||
return output;
|
||||
#else
|
||||
return default;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
432
X10D/src/Core/SpanExtensions.cs
Normal file
432
X10D/src/Core/SpanExtensions.cs
Normal file
@ -0,0 +1,432 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
using X10D.Core;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
#endif
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
using System.Diagnostics;
|
||||
#endif
|
||||
|
||||
namespace X10D.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="Span{T}"/> and <see cref="ReadOnlySpan{T}"/>.
|
||||
/// </summary>
|
||||
public static class SpanExtensions
|
||||
{
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
private const ulong IntegerPackingMagic = 0x0102040810204080;
|
||||
private static Vector64<ulong> IntegerPackingMagicV64
|
||||
{
|
||||
get => Vector64.Create(IntegerPackingMagic);
|
||||
}
|
||||
|
||||
private static Vector128<ulong> IntegerPackingMagicV128
|
||||
{
|
||||
get => Vector128.Create(IntegerPackingMagic);
|
||||
}
|
||||
|
||||
private static Vector256<ulong> IntegerPackingMagicV256
|
||||
{
|
||||
get => Vector256.Create(IntegerPackingMagic);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Indicate whether a specific enumeration value is found in a span.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="span">The span to search from.</param>
|
||||
/// <param name="value">The enum to search for.</param>
|
||||
/// <returns><see langword="true"/> if found, <see langword="false"/> otherwise.</returns>
|
||||
/// <exception cref="ArgumentException">Unexpected enum memory size.</exception>
|
||||
#if NETSTANDARD2_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#else
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
public static bool Contains<T>(this Span<T> span, T value) where T : struct, Enum
|
||||
{
|
||||
return Contains((ReadOnlySpan<T>)span, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicate whether a specific enumeration value is found in a span.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="span">The span to search from.</param>
|
||||
/// <param name="value">The enum to search for.</param>
|
||||
/// <returns><see langword="true"/> if found, <see langword="false"/> otherwise.</returns>
|
||||
/// <exception cref="ArgumentException">Unexpected enum memory size.</exception>
|
||||
#if NETSTANDARD2_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#else
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
public static bool Contains<T>(this ReadOnlySpan<T> span, T value) where T : struct, Enum
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
// Use MemoryMarshal.CreateSpan instead of using creating new Span instance from pointer will trim down a lot of
|
||||
// instructions on Release mode.
|
||||
|
||||
// Also use reference instead of MemoryMarshal.Cast to remove boundary check (or something, it just result in something
|
||||
// like that).
|
||||
|
||||
// TODO: Figure out some kind of way to directly pass the Span directly into Contains call, which make method smaller and
|
||||
// more prone to inlining...
|
||||
unsafe
|
||||
{
|
||||
#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
|
||||
switch (sizeof(T))
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
ref byte enums = ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span));
|
||||
return MemoryMarshal.CreateSpan(ref enums, span.Length).Contains(Unsafe.As<T, byte>(ref value));
|
||||
}
|
||||
|
||||
case 2:
|
||||
{
|
||||
ref ushort enums = ref Unsafe.As<T, ushort>(ref MemoryMarshal.GetReference(span));
|
||||
return MemoryMarshal.CreateSpan(ref enums, span.Length).Contains(Unsafe.As<T, ushort>(ref value));
|
||||
}
|
||||
|
||||
case 4:
|
||||
{
|
||||
ref uint enums = ref Unsafe.As<T, uint>(ref MemoryMarshal.GetReference(span));
|
||||
return MemoryMarshal.CreateSpan(ref enums, span.Length).Contains(Unsafe.As<T, uint>(ref value));
|
||||
}
|
||||
|
||||
case 8:
|
||||
{
|
||||
ref ulong enums = ref Unsafe.As<T, ulong>(ref MemoryMarshal.GetReference(span));
|
||||
return MemoryMarshal.CreateSpan(ref enums, span.Length).Contains(Unsafe.As<T, ulong>(ref value));
|
||||
}
|
||||
|
||||
default:
|
||||
#if NET7_0_OR_GREATER
|
||||
throw new UnreachableException($"Enum with the size of {Unsafe.SizeOf<T>()} bytes is unexpected.");
|
||||
#else
|
||||
throw new ArgumentException($"Enum with the size of {Unsafe.SizeOf<T>()} bytes is unexpected.");
|
||||
#endif
|
||||
}
|
||||
#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
|
||||
}
|
||||
#else
|
||||
foreach (var it in span)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(it, value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs a <see cref="Span{T}"/> of booleans into a <see cref="byte" />.
|
||||
/// </summary>
|
||||
/// <param name="source">The span of booleans to pack.</param>
|
||||
/// <returns>An 8-bit unsigned integer containing the packed booleans.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 8 elements.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte PackByte(this Span<bool> source)
|
||||
{
|
||||
return PackByte((ReadOnlySpan<bool>)source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs a <see cref="ReadOnlySpan{T}"/> of booleans into a <see cref="byte" />.
|
||||
/// </summary>
|
||||
/// <param name="source">The span of booleans to pack.</param>
|
||||
/// <returns>An 8-bit unsigned integer containing the packed booleans.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 8 elements.</exception>
|
||||
[Pure]
|
||||
public static unsafe byte PackByte(this ReadOnlySpan<bool> source)
|
||||
{
|
||||
switch (source.Length)
|
||||
{
|
||||
case > 8: throw new ArgumentException("Source cannot contain more than 8 elements.", nameof(source));
|
||||
case 8:
|
||||
#if NETSTANDARD2_1
|
||||
// TODO: Think of a way to do fast boolean correctness without using SIMD API.
|
||||
goto default;
|
||||
#else
|
||||
// TODO: Acceleration in Big Endian environment.
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
goto default;
|
||||
}
|
||||
|
||||
fixed (bool* pSource = source) {
|
||||
// TODO: .NET 8.0 Wasm support.
|
||||
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
var load = Sse2.LoadScalarVector128((ulong*)pSource).AsByte();
|
||||
|
||||
return unchecked((byte)(IntegerPackingMagic * load.CorrectBoolean().AsUInt64().GetElement(0) >> 56));
|
||||
}
|
||||
|
||||
// Probably should remove this piece of code because it is untested, but I see no reason why it should fail
|
||||
// unless vld1_u8 reverse positions of 8 bytes for some reason.
|
||||
|
||||
if (AdvSimd.IsSupported)
|
||||
{
|
||||
// Hasn't been tested since March 6th 2023 (Reason: Unavailable hardware).
|
||||
var load = AdvSimd.LoadVector64((byte*)pSource);
|
||||
|
||||
return unchecked((byte)(IntegerPackingMagic * load.CorrectBoolean().AsUInt64().GetElement(0) >> 56));
|
||||
}
|
||||
|
||||
goto default;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
byte result = 0;
|
||||
|
||||
for (var i = 0; i < source.Length; i++)
|
||||
{
|
||||
result |= (byte)(source[i] ? 1 << i : 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs a <see cref="Span{T}"/> of booleans into a <see cref="short" />.
|
||||
/// </summary>
|
||||
/// <param name="source">The span of booleans to pack.</param>
|
||||
/// <returns>A 16-bit signed integer containing the packed booleans.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 16 elements.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static short PackInt16(this Span<bool> source)
|
||||
{
|
||||
return PackInt16((ReadOnlySpan<bool>)source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs a <see cref="ReadOnlySpan{T}"/> of booleans into a <see cref="short" />.
|
||||
/// </summary>
|
||||
/// <param name="source">The span of booleans to pack.</param>
|
||||
/// <returns>A 16-bit signed integer containing the packed booleans.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 16 elements.</exception>
|
||||
[Pure]
|
||||
public static unsafe short PackInt16(this ReadOnlySpan<bool> source)
|
||||
{
|
||||
switch (source.Length)
|
||||
{
|
||||
case > 16: throw new ArgumentException("Source cannot contain more than than 16 elements.", nameof(source));
|
||||
case 8: return PackByte(source); // Potential optimization
|
||||
|
||||
case 16:
|
||||
#if NETSTANDARD2_1
|
||||
// TODO: Think of a way to do fast boolean correctness without using SIMD API.
|
||||
goto default;
|
||||
#else
|
||||
// TODO: Acceleration in Big Endian environment.
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
goto default;
|
||||
}
|
||||
|
||||
// TODO: AdvSimd implementation.
|
||||
// TODO: WasmSimd implementation.
|
||||
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
fixed (bool* pSource = source)
|
||||
{
|
||||
Vector128<byte> load = Sse2.LoadVector128((byte*)pSource);
|
||||
Vector128<ulong> correct = load.CorrectBoolean().AsUInt64();
|
||||
Vector128<ulong> multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV128, correct);
|
||||
Vector128<ulong> shift = Sse2.ShiftRightLogical(multiply, 56);
|
||||
|
||||
return (short)(shift.GetElement(0) | (shift.GetElement(1) << 8));
|
||||
}
|
||||
}
|
||||
|
||||
goto default;
|
||||
#endif
|
||||
|
||||
default:
|
||||
short result = 0;
|
||||
|
||||
for (var i = 0; i < source.Length; i++)
|
||||
{
|
||||
result |= (short)(source[i] ? 1 << i : 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs a <see cref="Span{T}"/> of booleans into a <see cref="int" />.
|
||||
/// </summary>
|
||||
/// <param name="source">The span of booleans to pack.</param>
|
||||
/// <returns>A 32-bit signed integer containing the packed booleans.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 32 elements.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int PackInt32(this Span<bool> source)
|
||||
{
|
||||
return PackInt32((ReadOnlySpan<bool>)source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs a <see cref="ReadOnlySpan{T}"/> of booleans into a <see cref="int" />.
|
||||
/// </summary>
|
||||
/// <param name="source">The span of booleans to pack.</param>
|
||||
/// <returns>A 32-bit signed integer containing the packed booleans.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 32 elements.</exception>
|
||||
[Pure]
|
||||
public static unsafe int PackInt32(this ReadOnlySpan<bool> source)
|
||||
{
|
||||
switch (source.Length)
|
||||
{
|
||||
case > 32: throw new ArgumentException("Source cannot contain more than than 32 elements.", nameof(source));
|
||||
case 8: return PackByte(source);
|
||||
case 16: return PackInt16(source);
|
||||
|
||||
case 32:
|
||||
#if NETSTANDARD2_1
|
||||
// TODO: Think of a way to do fast boolean correctness without using SIMD API.
|
||||
goto default;
|
||||
#else
|
||||
// TODO: Acceleration in Big Endian environment.
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
goto default;
|
||||
}
|
||||
|
||||
fixed (bool* pSource = source)
|
||||
{
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
Vector256<byte> load = Avx.LoadVector256((byte*)pSource);
|
||||
Vector256<ulong> correct = load.CorrectBoolean().AsUInt64();
|
||||
|
||||
Vector256<ulong> multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV256, correct);
|
||||
Vector256<ulong> shift = Avx2.ShiftRightLogical(multiply, 56);
|
||||
shift = Avx2.ShiftLeftLogicalVariable(shift, Vector256.Create(0UL, 8, 16, 24));
|
||||
|
||||
Vector256<ulong> p1 = Avx2.Permute4x64(shift, 0b10_11_00_01);
|
||||
Vector256<ulong> or1 = Avx2.Or(shift, p1);
|
||||
Vector256<ulong> p2 = Avx2.Permute4x64(or1, 0b00_00_10_10);
|
||||
Vector256<ulong> or2 = Avx2.Or(or1, p2);
|
||||
|
||||
return (int)or2.GetElement(0);
|
||||
}
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
Vector128<byte> load = Sse2.LoadVector128((byte*)pSource);
|
||||
Vector128<ulong> correct = load.CorrectBoolean().AsUInt64();
|
||||
|
||||
Vector128<ulong> multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV128, correct);
|
||||
Vector128<ulong> shift1 = Sse2.ShiftRightLogical(multiply, 56);
|
||||
shift1 = Sse2.ShiftLeftLogical(shift1, Vector128.Create(0UL, 8UL));
|
||||
|
||||
load = Sse2.LoadVector128((byte*)(pSource + 16));
|
||||
correct = load.CorrectBoolean().AsUInt64();
|
||||
|
||||
multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV128, correct);
|
||||
Vector128<ulong> shift2 = Sse2.ShiftRightLogical(multiply, 56);
|
||||
shift2 = Sse2.ShiftLeftLogical(shift2, Vector128.Create(16UL, 24UL));
|
||||
|
||||
Vector128<ulong> or1 = Sse2.Or(shift1, shift2);
|
||||
Vector128<ulong> or2 = Sse2.Or(or1, or1.ReverseElements());
|
||||
|
||||
return (int)or2.GetElement(0);
|
||||
}
|
||||
if (AdvSimd.IsSupported)
|
||||
{
|
||||
// Hasn't been tested since March 6th 2023 (Reason: Unavailable hardware).
|
||||
Vector128<ulong> vector1 = AdvSimd.LoadVector128((byte*)pSource).CorrectBoolean().AsUInt64();
|
||||
Vector128<ulong> vector2 = AdvSimd.LoadVector128((byte*)(pSource + 16)).CorrectBoolean().AsUInt64();
|
||||
|
||||
Vector128<ulong> calc1 = IntrinsicUtility.Multiply(IntegerPackingMagicV128, vector1);
|
||||
Vector128<ulong> calc2 = IntrinsicUtility.Multiply(IntegerPackingMagicV128, vector2);
|
||||
|
||||
calc1 = AdvSimd.ShiftRightLogical(calc1, 56);
|
||||
calc2 = AdvSimd.ShiftRightLogical(calc2, 56);
|
||||
|
||||
Vector128<ulong> shift1 = AdvSimd.ShiftLogical(calc1, Vector128.Create(0, 8));
|
||||
Vector128<ulong> shift2 = AdvSimd.ShiftLogical(calc2, Vector128.Create(16, 24));
|
||||
|
||||
return (int)(shift1.GetElement(0) | shift1.GetElement(1) | shift2.GetElement(0) | shift2.GetElement(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
goto default;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
int result = 0;
|
||||
|
||||
for (var i = 0; i < source.Length; i++)
|
||||
{
|
||||
result |= source[i] ? 1 << i : 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs a <see cref="Span{T}"/> of booleans into a <see cref="long" />.
|
||||
/// </summary>
|
||||
/// <param name="source">The span of booleans to pack.</param>
|
||||
/// <returns>A 64-bit signed integer containing the packed booleans.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 64 elements.</exception>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static long PackInt64(this Span<bool> source)
|
||||
{
|
||||
return PackInt64((ReadOnlySpan<bool>)source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packs a <see cref="ReadOnlySpan{T}"/> of booleans into a <see cref="long" />.
|
||||
/// </summary>
|
||||
/// <param name="source">The span of booleans to pack.</param>
|
||||
/// <returns>A 64-bit signed integer containing the packed booleans.</returns>
|
||||
/// <exception cref="ArgumentException"><paramref name="source" /> contains more than 64 elements.</exception>
|
||||
[Pure]
|
||||
public static unsafe long PackInt64(this ReadOnlySpan<bool> source)
|
||||
{
|
||||
switch (source.Length)
|
||||
{
|
||||
case > 64: throw new ArgumentException("Source cannot contain more than than 64 elements.", nameof(source));
|
||||
case 8: return PackByte(source);
|
||||
case 16: return PackInt16(source);
|
||||
case 32: return PackInt32(source);
|
||||
case 64:
|
||||
// TODO: Reimplement when Vector512 is in standard API.
|
||||
return (long)PackInt32(source[..32]) | ((long)PackInt32(source[32..]) << 32);
|
||||
|
||||
default:
|
||||
long result = 0;
|
||||
|
||||
for (var i = 0; i < source.Length; i++)
|
||||
{
|
||||
result |= source[i] ? 1U << i : 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
12
X10D/src/ExceptionMessages.Designer.cs
generated
12
X10D/src/ExceptionMessages.Designer.cs
generated
@ -1,6 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
@ -18,7 +19,7 @@ namespace X10D {
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class ExceptionMessages {
|
||||
@ -221,6 +222,15 @@ namespace X10D {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Rune.Utf8SequenceLength returns value {0} which is outside range 1 to 4 (inclusive), which is unexpected according to the official documentation..
|
||||
/// </summary>
|
||||
internal static string UnexpectedRuneUtf8SequenceLength {
|
||||
get {
|
||||
return ResourceManager.GetString("UnexpectedRuneUtf8SequenceLength", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Year cannot be zero..
|
||||
/// </summary>
|
||||
|
@ -1,83 +1,180 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
<data name="BufferTooSmall" xml:space="preserve">
|
||||
<value>The buffer is too small to contain the data.</value>
|
||||
</data>
|
||||
<data name="CountMustBeInRange" xml:space="preserve">
|
||||
<value>Count must be positive and count must refer to a location within the string/array/collection.</value>
|
||||
</data>
|
||||
<data name="EndIndexLessThanStartIndex" xml:space="preserve">
|
||||
<value>The end index must be greater than or equal to the start index.</value>
|
||||
</data>
|
||||
<data name="EndIndexGreaterThanCount" xml:space="preserve">
|
||||
<value>The end index must be less than the list count.</value>
|
||||
</data>
|
||||
<data name="TypeIsNotClass" xml:space="preserve">
|
||||
<value>{0} is not a class.</value>
|
||||
</data>
|
||||
<data name="TypeIsNotInterface" xml:space="preserve">
|
||||
<value>{0} is not an interface.</value>
|
||||
</data>
|
||||
<data name="TypeDoesNotInheritAttribute" xml:space="preserve">
|
||||
<value>{0} does not inherit {1}</value>
|
||||
</data>
|
||||
<data name="HashAlgorithmNoCreateMethod" xml:space="preserve">
|
||||
<value>HashAlgorithm does not offer Create method.</value>
|
||||
</data>
|
||||
<data name="HashAlgorithmCreateReturnedNull" xml:space="preserve">
|
||||
<value>HashAlgorithm's Create method returned null reference.</value>
|
||||
</data>
|
||||
<data name="IndexOutOfRange" xml:space="preserve">
|
||||
<value>Index was out of range. Must be non-negative and less than or equal to the size of the collection.</value>
|
||||
</data>
|
||||
<data name="LengthGreaterThanOrEqualTo0" xml:space="preserve">
|
||||
<value>Length must be greater than or equal to 0.</value>
|
||||
</data>
|
||||
<data name="StreamDoesNotSupportReading" xml:space="preserve">
|
||||
<value>The stream does not support reading.</value>
|
||||
</data>
|
||||
<data name="StreamDoesNotSupportWriting" xml:space="preserve">
|
||||
<value>The stream does not support writing.</value>
|
||||
</data>
|
||||
<data name="StreamTooLarge" xml:space="preserve">
|
||||
<value>The length of the stream is too large.</value>
|
||||
</data>
|
||||
<data name="MaxValueGreaterThanEqualTo0" xml:space="preserve">
|
||||
<value>maxValue must be greater than or equal to 0</value>
|
||||
</data>
|
||||
<data name="MaxValueGreaterThanEqualToMinValue" xml:space="preserve">
|
||||
<value>maxValue must be greater than or equal to minValue</value>
|
||||
</data>
|
||||
<data name="LowerCannotBeGreaterThanUpper" xml:space="preserve">
|
||||
<value>{0} cannot be greater than {1}</value>
|
||||
</data>
|
||||
<data name="CountMustBeGreaterThanOrEqualTo0" xml:space="preserve">
|
||||
<value>count must be greater than or equal to 0.</value>
|
||||
</data>
|
||||
<data name="YearCannotBeZero" xml:space="preserve">
|
||||
<value>Year cannot be zero.</value>
|
||||
</data>
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="BufferTooSmall" xml:space="preserve">
|
||||
<value>The buffer is too small to contain the data.</value>
|
||||
</data>
|
||||
<data name="CountMustBeInRange" xml:space="preserve">
|
||||
<value>Count must be positive and count must refer to a location within the string/array/collection.</value>
|
||||
</data>
|
||||
<data name="EndIndexLessThanStartIndex" xml:space="preserve">
|
||||
<value>The end index must be greater than or equal to the start index.</value>
|
||||
</data>
|
||||
<data name="EndIndexGreaterThanCount" xml:space="preserve">
|
||||
<value>The end index must be less than the list count.</value>
|
||||
</data>
|
||||
<data name="TypeIsNotClass" xml:space="preserve">
|
||||
<value>{0} is not a class.</value>
|
||||
</data>
|
||||
<data name="TypeIsNotInterface" xml:space="preserve">
|
||||
<value>{0} is not an interface.</value>
|
||||
</data>
|
||||
<data name="TypeDoesNotInheritAttribute" xml:space="preserve">
|
||||
<value>{0} does not inherit {1}</value>
|
||||
</data>
|
||||
<data name="HashAlgorithmNoCreateMethod" xml:space="preserve">
|
||||
<value>HashAlgorithm does not offer Create method.</value>
|
||||
</data>
|
||||
<data name="HashAlgorithmCreateReturnedNull" xml:space="preserve">
|
||||
<value>HashAlgorithm's Create method returned null reference.</value>
|
||||
</data>
|
||||
<data name="IndexOutOfRange" xml:space="preserve">
|
||||
<value>Index was out of range. Must be non-negative and less than or equal to the size of the collection.</value>
|
||||
</data>
|
||||
<data name="LengthGreaterThanOrEqualTo0" xml:space="preserve">
|
||||
<value>Length must be greater than or equal to 0.</value>
|
||||
</data>
|
||||
<data name="StreamDoesNotSupportReading" xml:space="preserve">
|
||||
<value>The stream does not support reading.</value>
|
||||
</data>
|
||||
<data name="StreamDoesNotSupportWriting" xml:space="preserve">
|
||||
<value>The stream does not support writing.</value>
|
||||
</data>
|
||||
<data name="StreamTooLarge" xml:space="preserve">
|
||||
<value>The length of the stream is too large.</value>
|
||||
</data>
|
||||
<data name="MaxValueGreaterThanEqualTo0" xml:space="preserve">
|
||||
<value>maxValue must be greater than or equal to 0</value>
|
||||
</data>
|
||||
<data name="MaxValueGreaterThanEqualToMinValue" xml:space="preserve">
|
||||
<value>maxValue must be greater than or equal to minValue</value>
|
||||
</data>
|
||||
<data name="LowerCannotBeGreaterThanUpper" xml:space="preserve">
|
||||
<value>{0} cannot be greater than {1}</value>
|
||||
</data>
|
||||
<data name="CountMustBeGreaterThanOrEqualTo0" xml:space="preserve">
|
||||
<value>count must be greater than or equal to 0.</value>
|
||||
</data>
|
||||
<data name="YearCannotBeZero" xml:space="preserve">
|
||||
<value>Year cannot be zero.</value>
|
||||
</data>
|
||||
<data name="UnexpectedRuneUtf8SequenceLength" xml:space="preserve">
|
||||
<value>Rune.Utf8SequenceLength returns value {0} which is outside range 1 to 4 (inclusive), which is unexpected according to the official documentation.</value>
|
||||
</data>
|
||||
</root>
|
@ -17,9 +17,9 @@ public static class DoubleExtensions
|
||||
[Pure]
|
||||
public static byte[] GetBytes(this double value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[8];
|
||||
byte[] buffer = new byte[8];
|
||||
value.TryWriteBytes(buffer);
|
||||
return buffer.ToArray();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -31,9 +31,9 @@ public static class DoubleExtensions
|
||||
[Pure]
|
||||
public static byte[] GetBytes(this double value, Endianness endianness)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[8];
|
||||
byte[] buffer = new byte[8];
|
||||
value.TryWriteBytes(buffer, endianness);
|
||||
return buffer.ToArray();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -16,9 +16,9 @@ public static class Int16Extensions
|
||||
[Pure]
|
||||
public static byte[] GetBytes(this short value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[2];
|
||||
byte[] buffer = new byte[2];
|
||||
value.TryWriteBytes(buffer);
|
||||
return buffer.ToArray();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -30,9 +30,9 @@ public static class Int16Extensions
|
||||
[Pure]
|
||||
public static byte[] GetBytes(this short value, Endianness endianness)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[2];
|
||||
byte[] buffer = new byte[2];
|
||||
value.TryWriteBytes(buffer, endianness);
|
||||
return buffer.ToArray();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -16,9 +16,9 @@ public static class Int32Extensions
|
||||
[Pure]
|
||||
public static byte[] GetBytes(this int value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
byte[] buffer = new byte[4];
|
||||
value.TryWriteBytes(buffer);
|
||||
return buffer.ToArray();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -30,7 +30,7 @@ public static class Int32Extensions
|
||||
[Pure]
|
||||
public static byte[] GetBytes(this int value, Endianness endianness)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[4];
|
||||
byte[] buffer = new byte[4];
|
||||
value.TryWriteBytes(buffer, endianness);
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
@ -158,7 +158,6 @@ public static class ListOfByteExtensions
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
#endif
|
||||
|
||||
return BitConverter.ToInt64(source.ToArray(), startIndex);
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ namespace X10D.Math;
|
||||
public static class ByteExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes the digital root of this 16-bit integer.
|
||||
/// Computes the digital root of this 8-bit integer.
|
||||
/// </summary>
|
||||
/// <param name="value">The value whose digital root to compute.</param>
|
||||
/// <returns>The digital root of <paramref name="value" />.</returns>
|
||||
|
@ -37,8 +37,7 @@ public static class MemberInfoExtensions
|
||||
throw new ArgumentNullException(nameof(member));
|
||||
}
|
||||
#endif
|
||||
|
||||
return member.HasCustomAttribute(typeof(T));
|
||||
return Attribute.IsDefined(member, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -82,7 +81,7 @@ public static class MemberInfoExtensions
|
||||
attribute, typeof(Attribute)), nameof(attribute));
|
||||
}
|
||||
|
||||
return member.GetCustomAttribute(attribute) is not null;
|
||||
return Attribute.IsDefined(member, attribute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,6 +1,9 @@
|
||||
#if NETCOREAPP3_0_OR_GREATER
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace X10D.Text;
|
||||
@ -44,17 +47,63 @@ public static class RuneExtensions
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
int utf8SequenceLength = value.Utf8SequenceLength;
|
||||
Span<byte> utf8 = stackalloc byte[utf8SequenceLength];
|
||||
value.EncodeToUtf8(utf8);
|
||||
int length = value.Utf8SequenceLength;
|
||||
|
||||
Span<byte> buffer = stackalloc byte[utf8.Length * count];
|
||||
for (var index = 0; index < count; index++)
|
||||
// Helpful documentation: https://en.wikipedia.org/wiki/UTF-8
|
||||
// This probably gonna break interning but whatever.
|
||||
switch (length)
|
||||
{
|
||||
utf8.CopyTo(buffer.Slice(index * utf8.Length, utf8.Length));
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// Codepoint 0 to 0x00FF can be directly turn into char value without any conversion.
|
||||
return new string((char)value.Value, count);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
// Codepoint 0x0080 to 0x07FF takes 2 UTF-8 bytes, and it can be represented by 1 UTF-16 character (.NET runtime use
|
||||
// UTF-16 encoding).
|
||||
// Source: https://stackoverflow.com/questions/63905684
|
||||
case 2:
|
||||
// Codepoint 0x0800 to 0xFFFF takes 3 UTF-8 bytes, and can also be represented by 1 UTF-16 character.
|
||||
case 3:
|
||||
{
|
||||
// Codepoint 0x0080 to 0x07FF convert into 1 .NET character string, directly use string constructor.
|
||||
unsafe
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[length];
|
||||
value.EncodeToUtf8(bytes);
|
||||
|
||||
char character;
|
||||
Encoding.UTF8.GetChars(bytes, new Span<char>(&character, 1));
|
||||
|
||||
return new string(character, count);
|
||||
}
|
||||
}
|
||||
|
||||
// Codepoint 0x10000 and beyond will takes **only** 2 UTF-16 character.
|
||||
case 4:
|
||||
{
|
||||
return string.Create(count * 2, value, (span, rune) =>
|
||||
{
|
||||
unsafe {
|
||||
Span<byte> bytes = stackalloc byte[4];
|
||||
value.EncodeToUtf8(bytes);
|
||||
|
||||
int characters; // 2 characters, fit inside 1 32-bit integer.
|
||||
Encoding.UTF8.GetChars(bytes, new Span<char>(&characters, 2));
|
||||
|
||||
MemoryMarshal.Cast<char, int>(span).Fill(characters);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
var msg = string.Format(CultureInfo.CurrentCulture, ExceptionMessages.UnexpectedRuneUtf8SequenceLength, length);
|
||||
#if NET7_0_OR_GREATER
|
||||
throw new UnreachableException(msg);
|
||||
#else
|
||||
throw new InvalidOperationException(msg);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user