1
0
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:
Oliver Booth 2023-03-23 01:06:59 +00:00 committed by GitHub
commit f8243923df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1762 additions and 151 deletions

1
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
ko_fi: oliverbooth
custom: ['https://buymeacoffee.com/oliverbooth']

View 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());
}
}

View File

@ -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()
{

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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>

View 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 &lt; 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 &lt; 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 &lt; 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

View 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

View 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;
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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();
}

View File

@ -158,7 +158,6 @@ public static class ListOfByteExtensions
throw new ArgumentNullException(nameof(source));
}
#endif
return BitConverter.ToInt64(source.ToArray(), startIndex);
}

View File

@ -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>

View File

@ -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>

View File

@ -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