Enhance mathematical capabilities and job system

Added new numeric types for unsigned integers, including uint2, uint3, and uint4, along with their matrix types.
Added a new `quaternion` struct with constructors and methods for creating and manipulating quaternions.
Added methods for projecting and reflecting vectors, enhancing geometric operations.
Added utility functions for generating orthonormal bases and changing vector signs.
Added comprehensive unit tests for new mathematical functions and quaternion operations.
Added a high-performance job scheduling system with job management features and worker thread management.
Added new structs for job execution, allowing efficient job scheduling and execution.
Added utility functions for job execution, including methods for obtaining unique job IDs.

Changed access modifiers and property definitions in several files for improved clarity and maintainability.
Changed property definitions and method implementations in `ImageInfo.cs`, `ImageResult.cs`, and `ImageResultFloat.cs` for better readability.
Changed memory management functions in `CRuntime.cs` and improved memory allocation tracking in `MemoryStats.cs`.
Changed the project file to include references to necessary projects and enable unsafe code blocks.

Removed the `WorkerThreadPool.cs` file, integrating worker thread management directly into the `JobScheduler`.
Removed the `float4` struct and its associated methods and properties, transitioning to a new code generation strategy.
Removed the `float4.tt` template and other related files, indicating a shift in code generation approach.
Removed the `Vectorize.cs` file, indicating a change in how vector operations are handled.

Updated the `.gitignore` file to include IDE-specific settings.
Updated various XML files to define project components and structure.
Updated the `AllocationManager.cs` to improve memory allocation management and introduce new strategies.
Updated the `UnsafeArray.cs`, `UnsafeHashMap.cs`, and `UnsafeList.cs` to enhance performance and safety in unsafe contexts.
Updated error handling and function pointer management in `MemoryLeakException.cs` and `FunctionPointer.cs`.
Updated the `AssemblyInfo.cs` file to include global using directives for better code organization.
This commit is contained in:
2025-09-06 12:07:02 +09:00
parent eeff3313b5
commit a2a760594e
114 changed files with 20826 additions and 7217 deletions

13
.idea/.idea.Misaki.HighPerformance/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/contentModel.xml
/modules.xml
/projectSettingsUpdater.xml
/.idea.Misaki.HighPerformance.iml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,12 +1,9 @@
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image;
public class AnimatedFrameResult : ImageResult
{ {
#if !STBSHARP_INTERNAL public int DelayInMs
public
#else
internal
#endif
class AnimatedFrameResult : ImageResult
{ {
public int DelayInMs { get; set; } get; set;
} }
} }

View File

@@ -4,8 +4,8 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image;
{
internal class AnimatedGifEnumerator : IEnumerator<AnimatedFrameResult> internal class AnimatedGifEnumerator : IEnumerator<AnimatedFrameResult>
{ {
private readonly StbImage.stbi__context _context; private readonly StbImage.stbi__context _context;
@@ -62,19 +62,15 @@ namespace Misaki.HighPerformance.Image
if (result == null) if (result == null)
return false; return false;
if (Current == null) Current ??= new AnimatedFrameResult
{
Current = new AnimatedFrameResult
{ {
Data = result,
Width = (uint)_gif.w, Width = (uint)_gif.w,
Height = (uint)_gif.h, Height = (uint)_gif.h,
SourceComponent = (ColorComponents)ccomp, SourceComp = (ColorComponents)ccomp,
Component = ColorComponents == ColorComponents.Default ? (ColorComponents)ccomp : ColorComponents Comp = ColorComponents == ColorComponents.Default ? (ColorComponents)ccomp : ColorComponents
}; };
Current.SetData(result);
}
Current.DelayInMs = _gif.delay; Current.DelayInMs = _gif.delay;
return true; return true;
@@ -146,4 +142,3 @@ namespace Misaki.HighPerformance.Image
return GetEnumerator(); return GetEnumerator();
} }
} }
}

View File

@@ -1,11 +1,6 @@
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image;
{
#if !STBSHARP_INTERNAL public enum ColorComponents
public
#else
internal
#endif
enum ColorComponents
{ {
Default, Default,
R, R,
@@ -13,4 +8,3 @@
RGB, RGB,
RGBA RGBA
} }
}

View File

@@ -1,13 +1,8 @@
using System.IO; using System.IO;
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image;
{
#if !STBSHARP_INTERNAL public struct ImageInfo
public
#else
internal
#endif
struct ImageInfo
{ {
public int Width; public int Width;
public int Height; public int Height;
@@ -37,4 +32,3 @@ namespace Misaki.HighPerformance.Image
}; };
} }
} }
}

View File

@@ -2,13 +2,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Image; namespace Misaki.HighPerformance.Image;
public unsafe class ImageResult : IDisposable public unsafe class ImageResult : IDisposable
{ {
private byte* _buffer; public byte* Data
{
get; init;
}
public uint Width public uint Width
{ {
@@ -20,22 +22,27 @@ public unsafe class ImageResult : IDisposable
get; init; get; init;
} }
public ColorComponents SourceComponent public ColorComponents SourceComp
{ {
get; init; get; init;
} }
public ColorComponents Component public ColorComponents Comp
{ {
get; init; get; init;
} }
public Span<byte> Data => new(_buffer, (int)(Width * Height * (uint)Component)); public ulong Size
internal void SetData(byte* data)
{ {
CRuntime.free(_buffer); get
_buffer = data; {
if (Data == null)
{
return 0;
}
return (ulong)(Width * Height * (int)Comp);
}
} }
internal static unsafe ImageResult FromResult(byte* result, uint width, uint height, ColorComponents comp, internal static unsafe ImageResult FromResult(byte* result, uint width, uint height, ColorComponents comp,
@@ -46,14 +53,13 @@ public unsafe class ImageResult : IDisposable
var image = new ImageResult var image = new ImageResult
{ {
Data = result,
Width = width, Width = width,
Height = height, Height = height,
SourceComponent = comp, SourceComp = comp,
Component = req_comp == ColorComponents.Default ? comp : req_comp Comp = req_comp == ColorComponents.Default ? comp : req_comp
}; };
image._buffer = result;
return image; return image;
} }
@@ -63,6 +69,7 @@ public unsafe class ImageResult : IDisposable
int x, y, comp; int x, y, comp;
var context = new StbImage.stbi__context(stream); var context = new StbImage.stbi__context(stream);
var result = StbImage.stbi__load_and_postprocess_8bit(context, &x, &y, &comp, (int)requiredComponents); var result = StbImage.stbi__load_and_postprocess_8bit(context, &x, &y, &comp, (int)requiredComponents);
return FromResult(result, (uint)x, (uint)y, (ColorComponents)comp, requiredComponents); return FromResult(result, (uint)x, (uint)y, (ColorComponents)comp, requiredComponents);
@@ -74,25 +81,24 @@ public unsafe class ImageResult : IDisposable
return FromStream(stream, requiredComponents); return FromStream(stream, requiredComponents);
} }
public static IEnumerable<AnimatedFrameResult> AnimatedGifFramesFromStream(Stream stream, ColorComponents requiredComponents = ColorComponents.Default) public static IEnumerable<AnimatedFrameResult> AnimatedGifFramesFromStream(Stream stream,
ColorComponents requiredComponents = ColorComponents.Default)
{ {
return new AnimatedGifEnumerable(stream, requiredComponents); return new AnimatedGifEnumerable(stream, requiredComponents);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] public Span<byte> AsSpan()
public byte* GetUnsafePtr()
{ {
return _buffer; if (Data == null)
{
return Span<byte>.Empty;
}
return new Span<byte>(Data, (int)Size);
} }
public void Dispose() public void Dispose()
{ {
if (_buffer == null) CRuntime.free(Data);
{
return;
}
CRuntime.free(_buffer);
_buffer = null;
} }
} }

View File

@@ -1,52 +1,66 @@
using Misaki.HighPerformance.Image.Runtime; using Misaki.HighPerformance.Image.Runtime;
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Image; namespace Misaki.HighPerformance.Image;
public unsafe class ImageResultFloat : IDisposable public unsafe class ImageResultFloat : IDisposable
{ {
private float* _buffer; public float* Data
public int Width
{ {
get; init; get; init;
} }
public int Height public uint Width
{ {
get; init; get; init;
} }
public ColorComponents SourceComponent public uint Height
{ {
get; init; get; init;
} }
public ColorComponents Component public ColorComponents SourceComp
{ {
get; init; get; init;
} }
public Span<byte> Data => new(_buffer, (int)(Width * Height * (uint)Component)); public ColorComponents Comp
{
get; init;
}
internal static unsafe ImageResultFloat FromResult(float* result, int width, int height, ColorComponents comp, public ulong Size
{
get
{
if (Data == null)
{
return 0;
}
return (ulong)(Width * Height * (int)Comp);
}
}
internal static unsafe ImageResultFloat FromResult(float* result, uint width, uint height, ColorComponents comp,
ColorComponents req_comp) ColorComponents req_comp)
{ {
if (result == null) if (result == null)
{
throw new InvalidOperationException(StbImage.stbi__g_failure_reason); throw new InvalidOperationException(StbImage.stbi__g_failure_reason);
}
var image = new ImageResultFloat var image = new ImageResultFloat
{ {
Data = result,
Width = width, Width = width,
Height = height, Height = height,
SourceComponent = comp, SourceComp = comp,
Component = req_comp == ColorComponents.Default ? comp : req_comp Comp = req_comp == ColorComponents.Default ? comp : req_comp
}; };
image._buffer = result;
return image; return image;
} }
@@ -56,9 +70,10 @@ public unsafe class ImageResultFloat : IDisposable
int x, y, comp; int x, y, comp;
var context = new StbImage.stbi__context(stream); var context = new StbImage.stbi__context(stream);
var result = StbImage.stbi__loadf_main(context, &x, &y, &comp, (int)requiredComponents); var result = StbImage.stbi__loadf_main(context, &x, &y, &comp, (int)requiredComponents);
return FromResult(result, x, y, (ColorComponents)comp, requiredComponents); return FromResult(result, (uint)x, (uint)y, (ColorComponents)comp, requiredComponents);
} }
public static ImageResultFloat FromMemory(byte[] data, public static ImageResultFloat FromMemory(byte[] data,
@@ -70,20 +85,18 @@ public unsafe class ImageResultFloat : IDisposable
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] public Span<byte> AsSpan()
public float* GetUnsafePtr()
{ {
return _buffer; if (Data == null)
{
return Span<byte>.Empty;
}
return new Span<byte>(Data, (int)Size);
} }
public void Dispose() public void Dispose()
{ {
if (_buffer == null) CRuntime.free(Data);
{
return;
}
CRuntime.free(_buffer);
_buffer = null;
} }
} }

View File

@@ -1,8 +1,8 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Image.Runtime namespace Misaki.HighPerformance.Image.Runtime;
{
internal static unsafe class CRuntime internal static unsafe class CRuntime
{ {
private static readonly string numbers = "0123456789"; private static readonly string numbers = "0123456789";
@@ -77,7 +77,10 @@ namespace Misaki.HighPerformance.Image.Runtime
public static void memset(void* ptr, int value, long size) public static void memset(void* ptr, int value, long size)
{ {
NativeMemory.Fill(ptr, (nuint)size, (byte)value); var bptr = (byte*)ptr;
var bval = (byte)value;
for (long i = 0; i < size; ++i)
*bptr++ = bval;
} }
public static void memset(void* ptr, int value, ulong size) public static void memset(void* ptr, int value, ulong size)
@@ -87,7 +90,7 @@ namespace Misaki.HighPerformance.Image.Runtime
public static uint _lrotl(uint x, int y) public static uint _lrotl(uint x, int y)
{ {
return x << y | x >> 32 - y; return (x << y) | (x >> (32 - y));
} }
public static void* realloc(void* ptr, long newSize) public static void* realloc(void* ptr, long newSize)
@@ -183,4 +186,3 @@ namespace Misaki.HighPerformance.Image.Runtime
return result; return result;
} }
} }
}

View File

@@ -1,8 +1,7 @@
// Generated by Sichem at 9/16/2024 9:09:30 AM // Generated by Sichem at 9/16/2024 9:09:30 AM
using System.Runtime.InteropServices;
using Misaki.HighPerformance.Image.Runtime; using Misaki.HighPerformance.Image.Runtime;
using Misaki.HighPerformance.Image; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image
{ {

View File

@@ -1,8 +1,7 @@
// Generated by Sichem at 12/24/2021 8:28:15 PM // Generated by Sichem at 12/24/2021 8:28:15 PM
using System.Runtime.InteropServices;
using Misaki.HighPerformance.Image.Runtime; using Misaki.HighPerformance.Image.Runtime;
using Misaki.HighPerformance.Image; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image
{ {

View File

@@ -3,7 +3,6 @@
using Misaki.HighPerformance.Image.Runtime; using Misaki.HighPerformance.Image.Runtime;
// Generated by Sichem at 12/24/2021 8:28:15 PM // Generated by Sichem at 12/24/2021 8:28:15 PM
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image
{ {

View File

@@ -289,16 +289,13 @@ namespace Misaki.HighPerformance.Image
color); color);
final = (byte*)stbi__malloc_mad3((int)a.s.img_x, (int)a.s.img_y, out_bytes, 0); final = (byte*)stbi__malloc_mad3((int)a.s.img_x, (int)a.s.img_y, out_bytes, 0);
if (final == null) if (final == null)
{
return stbi__err("outofmem"); return stbi__err("outofmem");
} for (p = 0; p < 7; ++p)
{
var xorig = stackalloc int[] { 0, 4, 0, 2, 0, 1, 0 }; var xorig = stackalloc int[] { 0, 4, 0, 2, 0, 1, 0 };
var yorig = stackalloc int[] { 0, 0, 4, 0, 2, 0, 1 }; var yorig = stackalloc int[] { 0, 0, 4, 0, 2, 0, 1 };
var xspc = stackalloc int[] { 8, 8, 4, 4, 2, 2, 1 }; var xspc = stackalloc int[] { 8, 8, 4, 4, 2, 2, 1 };
var yspc = stackalloc int[] { 8, 8, 8, 4, 4, 2, 2 }; var yspc = stackalloc int[] { 8, 8, 8, 4, 4, 2, 2 };
for (p = 0; p < 7; ++p)
{
var i = 0; var i = 0;
var j = 0; var j = 0;
var x = 0; var x = 0;

View File

@@ -3,7 +3,6 @@
using Misaki.HighPerformance.Image.Runtime; using Misaki.HighPerformance.Image.Runtime;
// Generated by Sichem at 12/24/2021 8:28:15 PM // Generated by Sichem at 12/24/2021 8:28:15 PM
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image
{ {

View File

@@ -3,7 +3,6 @@
using Misaki.HighPerformance.Image.Runtime; using Misaki.HighPerformance.Image.Runtime;
// Generated by Sichem at 12/24/2021 8:28:15 PM // Generated by Sichem at 12/24/2021 8:28:15 PM
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image
{ {
@@ -46,7 +45,8 @@ namespace Misaki.HighPerformance.Image
if (sz != 8 && sz != 15 && sz != 16 && sz != 24 && sz != 32) if (sz != 8 && sz != 15 && sz != 16 && sz != 24 && sz != 32)
goto errorEnd; goto errorEnd;
res = 1; res = 1;
errorEnd:; errorEnd:
;
stbi__rewind(s); stbi__rewind(s);
return res; return res;
} }

View File

@@ -2,7 +2,6 @@
using Misaki.HighPerformance.Image.Runtime; using Misaki.HighPerformance.Image.Runtime;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image
{ {

View File

@@ -2,16 +2,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image namespace Misaki.HighPerformance.Image;
{
#if !STBSHARP_INTERNAL public static unsafe partial class StbImage
public
#else
internal
#endif
static unsafe partial class StbImage
{ {
public static string stbi__g_failure_reason; public static string stbi__g_failure_reason;
public static readonly char[] stbi__parse_png_file_invalid_chunk = new char[25]; public static readonly char[] stbi__parse_png_file_invalid_chunk = new char[25];
@@ -60,7 +54,8 @@ namespace Misaki.HighPerformance.Image
public static byte stbi__get8(stbi__context s) public static byte stbi__get8(stbi__context s)
{ {
var b = s.Stream.ReadByte(); var b = s.Stream.ReadByte();
if (b == -1) return 0; if (b == -1)
return 0;
return (byte)b; return (byte)b;
} }
@@ -92,4 +87,3 @@ namespace Misaki.HighPerformance.Image
return result; return result;
} }
} }
}

View File

@@ -0,0 +1,26 @@
namespace Misaki.HighPerformance.Jobs;
/// <summary>
/// Represents a job that performs a single unit of work.
/// Jobs are structs to avoid allocations and enable high-performance execution.
/// </summary>
public interface IJob
{
/// <summary>
/// Executes the job logic.
/// </summary>
void Execute();
}
/// <summary>
/// Represents a job that performs the same operation for a set of items, executed in parallel.
/// Each job instance processes a range of indices, enabling data parallelism.
/// </summary>
public interface IJobParallelFor
{
/// <summary>
/// Executes the job for a single item at the given index.
/// </summary>
/// <param name="index">The index of the item to process.</param>
void Execute(int index);
}

View File

@@ -0,0 +1,24 @@
namespace Misaki.HighPerformance.Jobs;
/// <summary>
/// Base class for all jobs. Jobs are now classes to avoid heap allocation complexities.
/// </summary>
public abstract class JobBase
{
/// <summary>
/// Called when the job should be executed.
/// </summary>
public abstract void Execute();
}
/// <summary>
/// Base class for parallel jobs.
/// </summary>
public abstract class ParallelJobBase
{
/// <summary>
/// Called for each item in the parallel job.
/// </summary>
/// <param name="index">The index of the current item.</param>
public abstract void Execute(int index);
}

View File

@@ -0,0 +1,124 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Jobs;
/// <summary>
/// Internal data structure representing job ranges for parallel execution.
/// This matches Unity's JobRanges structure for work stealing.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct JobRanges
{
public int JobIndex;
public int BeginIndex;
public int EndIndex;
public int TotalLength;
public int BatchSize;
/// <summary>
/// Pointer to atomic counter for work stealing.
/// </summary>
public int* CurrentIndex;
}
/// <summary>
/// Internal job data structure that holds job execution information.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct JobData
{
/// <summary>
/// Unique identifier for this job.
/// </summary>
public ulong Id;
/// <summary>
/// Version counter to detect reused job slots.
/// </summary>
public int Version;
/// <summary>
/// Job state using atomic operations.
/// 0 = Scheduled, 1 = Running, 2 = Completed
/// </summary>
public int State;
/// <summary>
/// Number of dependencies this job has.
/// </summary>
public int DependencyCount;
/// <summary>
/// Number of completed dependencies.
/// </summary>
public int CompletedDependencies;
/// <summary>
/// Type of job (0 = IJob, 1 = IJobParallelFor).
/// </summary>
public JobType JobType;
/// <summary>
/// Function pointer to the job execution method.
/// </summary>
public ExecuteJobDelegate? ExecuteJobFunction;
/// <summary>
/// Function pointer to the parallel job execution method.
/// </summary>
public ExecuteParallelJobDelegate? ExecuteParallelJobFunction;
/// <summary>
/// Reference to the job data object.
/// </summary>
public object? JobDataObject; /// <summary>
/// For parallel jobs, the total number of iterations.
/// </summary>
public int TotalIterations;
/// <summary>
/// For parallel jobs, the batch size per worker.
/// </summary>
public int BatchSize;
/// <summary>
/// Array of dependency job IDs (inline for small counts).
/// </summary>
public fixed ulong Dependencies[8]; // Inline dependencies for performance
/// <summary>
/// Pointer to additional dependencies if more than 8.
/// </summary>
public ulong* AdditionalDependencies;
/// <summary>
/// Size of additional dependencies array.
/// </summary>
public int AdditionalDependencyCount;
public readonly bool IsCompleted => Volatile.Read(ref Unsafe.AsRef<int>(in State)) == 2;
public readonly bool CanExecute =>
Volatile.Read(ref Unsafe.AsRef<int>(in State)) == 0 &&
Volatile.Read(ref Unsafe.AsRef<int>(in CompletedDependencies)) >= DependencyCount;
}
/// <summary>
/// Type of job being executed.
/// </summary>
internal enum JobType : byte
{
Job = 0,
ParallelFor = 1
}
/// <summary>
/// Function pointer delegate for IJob execution.
/// </summary>
internal delegate void ExecuteJobDelegate(object jobData);
/// <summary>
/// Function pointer delegate for IJobParallelFor execution.
/// </summary>
internal unsafe delegate void ExecuteParallelJobDelegate(object jobData, ref JobRanges ranges, int jobIndex);

View File

@@ -0,0 +1,50 @@
namespace Misaki.HighPerformance.Jobs;
/// <summary>
/// Extension methods for scheduling jobs in a more user-friendly way.
/// These methods provide the public API for the job system.
/// </summary>
public static class JobExtensions
{
/// <summary>
/// Schedules an IJob for execution.
/// </summary>
/// <typeparam name="T">The job type implementing IJob.</typeparam>
/// <param name="jobData">The job data to execute.</param>
/// <param name="dependsOn">Optional job handle this job depends on.</param>
/// <returns>A job handle that can be used to wait for completion or create dependencies.</returns>
public static JobHandle Schedule<T>(this T jobData, JobHandle dependsOn = default)
where T : class, IJob
{
return JobStruct<T>.Schedule(jobData, dependsOn);
}
/// <summary>
/// Schedules an IJobParallelFor for parallel execution.
/// </summary>
/// <typeparam name="T">The job type implementing IJobParallelFor.</typeparam>
/// <param name="jobData">The job data to execute.</param>
/// <param name="arrayLength">The total number of iterations to execute.</param>
/// <param name="innerLoopBatchCount">The batch size for each worker thread. If 0 or negative, an optimal batch size will be calculated.</param>
/// <param name="dependsOn">Optional job handle this job depends on.</param>
/// <returns>A job handle that can be used to wait for completion or create dependencies.</returns>
public static JobHandle ScheduleParallel<T>(this T jobData, int arrayLength, int innerLoopBatchCount = 0, JobHandle dependsOn = default)
where T : class, IJobParallelFor
{
return ParallelForJobStruct<T>.ScheduleParallel(jobData, arrayLength, innerLoopBatchCount, dependsOn);
}
/// <summary>
/// Schedules an IJobParallelFor for parallel execution with automatic batch size calculation.
/// </summary>
/// <typeparam name="T">The job type implementing IJobParallelFor.</typeparam>
/// <param name="jobData">The job data to execute.</param>
/// <param name="arrayLength">The total number of iterations to execute.</param>
/// <param name="dependsOn">Optional job handle this job depends on.</param>
/// <returns>A job handle that can be used to wait for completion or create dependencies.</returns>
public static JobHandle ScheduleParallel<T>(this T jobData, int arrayLength, JobHandle dependsOn)
where T : class, IJobParallelFor
{
return ParallelForJobStruct<T>.ScheduleParallel(jobData, arrayLength, 0, dependsOn);
}
}

View File

@@ -0,0 +1,94 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Jobs;
/// <summary>
/// A handle that represents a scheduled job and can be used to manage dependencies and wait for completion.
/// JobHandle is designed to be a lightweight value type to avoid allocations.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public readonly struct JobHandle : IEquatable<JobHandle>
{
internal readonly ulong _id;
internal readonly int _version;
internal JobHandle(ulong id, int version)
{
_id = id;
_version = version;
}
/// <summary>
/// A completed job handle that can be used as a dependency that is already satisfied.
/// </summary>
public static JobHandle Completed => new(0, 0);
/// <summary>
/// Gets whether this job handle represents a completed job.
/// </summary>
public bool IsCompleted => _id == 0 || JobScheduler.IsCompleted(this);
/// <summary>
/// Blocks the calling thread until the job completes.
/// </summary>
public void Complete()
{
if (_id != 0)
{
JobScheduler.Complete(this);
}
}
/// <summary>
/// Combines multiple job handles into a single dependency.
/// The resulting handle will be complete when all input handles are complete.
/// </summary>
/// <param name="dependencies">The job handles to combine.</param>
/// <returns>A new job handle that depends on all input handles.</returns>
public static JobHandle CombineDependencies(params ReadOnlySpan<JobHandle> dependencies)
{
if (dependencies.Length == 0)
{
return Completed;
}
if (dependencies.Length == 1)
{
return dependencies[0];
}
return JobScheduler.CombineDependencies(dependencies);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(JobHandle other)
{
return _id == other._id && _version == other._version;
}
public override bool Equals(object? obj)
{
return obj is JobHandle other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_id, _version);
}
public static bool operator ==(JobHandle left, JobHandle right)
{
return left.Equals(right);
}
public static bool operator !=(JobHandle left, JobHandle right)
{
return !left.Equals(right);
}
public override string ToString()
{
return _id == 0 ? "JobHandle(Completed)" : $"JobHandle(ID:{_id}, Version:{_version})";
}
}

View File

@@ -0,0 +1,455 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Collections.Concurrent;
namespace Misaki.HighPerformance.Jobs;
/// <summary>
/// High-performance job scheduler that manages job execution and dependencies.
/// Designed to minimize allocations and provide efficient work distribution.
/// </summary>
public static unsafe class JobScheduler
{
private const int _Init_INLINE_JOBS = 64;
private const int _MAX_WORKER_THREADS = 64;
private static readonly Lock _lock = new();
private static SlotMap<JobData>? _jobPool;
private static int _jobVersion;
private static volatile bool _isInitialized;
private static volatile bool _isShuttingDown;
// Worker thread management
private static Thread[]? _workerThreads;
private static int _workerThreadCount;
private static readonly ManualResetEventSlim _workAvailableEvent = new ManualResetEventSlim(false);
private static readonly ConcurrentQueue<int> _readyJobs = new ConcurrentQueue<int>();
// Fast lookup for active jobs
private static readonly ConcurrentDictionary<ulong, int> _activeJobs = new ConcurrentDictionary<ulong, int>();
static JobScheduler()
{
Initialize();
}
/// <summary>
/// Initializes the job scheduler with default settings.
/// </summary>
public static void Initialize(int InitialJobsSize = _Init_INLINE_JOBS, int workerThreadCount = -1)
{
if (_isInitialized)
return;
lock (_lock)
{
if (_isInitialized)
return;
_jobPool = new SlotMap<JobData>(InitialJobsSize);
// Initialize worker threads
if (workerThreadCount <= 0)
workerThreadCount = Math.Min(Environment.ProcessorCount, _MAX_WORKER_THREADS);
_workerThreadCount = workerThreadCount;
_workerThreads = new Thread[workerThreadCount];
for (var i = 0; i < workerThreadCount; i++)
{
var thread = new Thread(WorkerThreadLoop)
{
Name = $"JobWorker-{i}",
IsBackground = true
};
_workerThreads[i] = thread;
thread.Start(i);
}
_isInitialized = true;
}
}
/// <summary>
/// Shuts down the job scheduler and cleans up resources.
/// </summary>
public static void Shutdown()
{
if (!_isInitialized || _isShuttingDown)
return;
lock (_lock)
{
if (_isShuttingDown)
return;
_isShuttingDown = true;
// Signal all worker threads to exit
_workAvailableEvent.Set();
// Wait for all worker threads to complete
if (_workerThreads != null)
{
for (var i = 0; i < _workerThreadCount; i++)
{
_workerThreads[i]?.Join(5000); // 5 second timeout
}
}
_jobPool?.Clear();
_jobPool = null;
_isInitialized = false;
_isShuttingDown = false;
}
}
/// <summary>
/// Schedules a job for execution.
/// </summary>
internal static JobHandle ScheduleJob(object jobData, ExecuteJobDelegate executeFunction, JobType jobType,
int totalIterations, int batchSize, JobHandle dependsOn)
{
if (!_isInitialized)
throw new InvalidOperationException("JobScheduler is not initialized");
var jobSlot = AllocateJobSlot();
var jobId = JobsUtility.GetNextJobId();
var version = Interlocked.Increment(ref _jobVersion);
ref var job = ref _jobPool![jobSlot];
job.Id = jobId;
job.Version = version;
job.State = 0; // Scheduled
job.JobType = jobType;
job.ExecuteJobFunction = executeFunction;
job.ExecuteParallelJobFunction = null;
job.JobDataObject = jobData;
job.TotalIterations = totalIterations;
job.BatchSize = batchSize;
job.DependencyCount = dependsOn._id == 0 ? 0 : 1;
job.CompletedDependencies = 0;
job.AdditionalDependencies = null;
job.AdditionalDependencyCount = 0;
// Set up dependencies
if (dependsOn._id != 0)
{
job.Dependencies[0] = dependsOn._id;
}
_activeJobs.TryAdd(jobId, jobSlot);
// Check if job can be executed immediately
if (job.CanExecute)
{
_readyJobs.Enqueue(jobSlot);
_workAvailableEvent.Set();
}
return new JobHandle(jobId, version);
}
/// <summary>
/// Schedules a parallel job for execution.
/// </summary>
internal static JobHandle ScheduleParallelJob(object jobData, ExecuteParallelJobDelegate executeFunction,
int totalIterations, int batchSize, JobHandle dependsOn)
{
if (!_isInitialized)
throw new InvalidOperationException("JobScheduler is not initialized");
var jobSlot = AllocateJobSlot();
var jobId = JobsUtility.GetNextJobId();
var version = Interlocked.Increment(ref _jobVersion);
ref var job = ref _jobPool![jobSlot];
job.Id = jobId;
job.Version = version;
job.State = 0; // Scheduled
job.JobType = JobType.ParallelFor;
job.ExecuteJobFunction = null;
job.ExecuteParallelJobFunction = executeFunction;
job.JobDataObject = jobData;
job.TotalIterations = totalIterations;
job.BatchSize = batchSize;
job.DependencyCount = dependsOn._id == 0 ? 0 : 1;
job.CompletedDependencies = 0;
job.AdditionalDependencies = null;
job.AdditionalDependencyCount = 0;
// Set up dependencies
if (dependsOn._id != 0)
{
job.Dependencies[0] = dependsOn._id;
}
_activeJobs.TryAdd(jobId, jobSlot);
// Check if job can be executed immediately
if (job.CanExecute)
{
_readyJobs.Enqueue(jobSlot);
_workAvailableEvent.Set();
}
return new JobHandle(jobId, version);
}
/// <summary>
/// Combines multiple job dependencies into a single handle.
/// </summary>
internal static JobHandle CombineDependencies(ReadOnlySpan<JobHandle> dependencies)
{
if (dependencies.Length == 0)
{
return JobHandle.Completed;
}
if (dependencies.Length == 1)
{
return dependencies[0];
}
// Filter out completed dependencies
var activeDeps = stackalloc JobHandle[dependencies.Length];
var activeCount = 0;
for (var i = 0; i < dependencies.Length; i++)
{
if (dependencies[i]._id != 0 && !IsCompleted(dependencies[i]))
{
activeDeps[activeCount++] = dependencies[i];
}
}
if (activeCount == 0)
return JobHandle.Completed;
if (activeCount == 1)
return activeDeps[0];
// Create a combined dependency job
var jobSlot = AllocateJobSlot();
var jobId = JobsUtility.GetNextJobId();
var version = Interlocked.Increment(ref _jobVersion);
ref var job = ref _jobPool![jobSlot];
job.Id = jobId;
job.Version = version;
job.State = 0; // Scheduled
job.JobType = JobType.Job; // Dependency-only job
job.ExecuteJobFunction = null; // No execution needed
job.ExecuteParallelJobFunction = null;
job.JobDataObject = null;
job.TotalIterations = 0;
job.BatchSize = 0;
job.DependencyCount = activeCount;
job.CompletedDependencies = 0;
// Set up dependencies
for (var i = 0; i < Math.Min(activeCount, 8); i++)
{
job.Dependencies[i] = activeDeps[i]._id;
}
// Handle additional dependencies if more than 8
if (activeCount > 8)
{
var additionalSize = activeCount - 8;
var handle = AllocationManager.GetAllocationHandle(Allocator.Temp);
job.AdditionalDependencies = (ulong*)handle.Alloc(handle.Allocator, (nuint)(sizeof(ulong) * additionalSize), sizeof(ulong), AllocationOption.None);
job.AdditionalDependencyCount = additionalSize;
for (var i = 0; i < additionalSize; i++)
{
job.AdditionalDependencies[i] = activeDeps[i + 8]._id;
}
}
_activeJobs.TryAdd(jobId, jobSlot);
return new JobHandle(jobId, version);
}
/// <summary>
/// Checks if a job is completed.
/// </summary>
internal static bool IsCompleted(JobHandle handle)
{
if (handle._id == 0)
return true;
if (_activeJobs.TryGetValue(handle._id, out var jobSlot))
{
return _jobPool![jobSlot].IsCompleted;
}
return true; // Job not found, assume completed
}
/// <summary>
/// Blocks until the specified job completes.
/// </summary>
internal static void Complete(JobHandle handle)
{
if (handle._id == 0)
return;
while (!IsCompleted(handle))
{
// Try to help with work while waiting
if (_readyJobs.TryDequeue(out var jobSlot))
{
ExecuteJob(jobSlot);
}
else
{
Thread.Yield();
}
}
}
private static int AllocateJobSlot()
{
// Create a new JobData and add it to the SlotMap
var jobData = new JobData();
return _jobPool!.Add(jobData);
}
private static void WorkerThreadLoop(object? threadIndexObj)
{
var threadIndex = (int)threadIndexObj!;
while (!_isShuttingDown)
{
if (_readyJobs.TryDequeue(out var jobSlot))
{
ExecuteJob(jobSlot);
}
else
{
_workAvailableEvent.Wait(100); // Wait with timeout
_workAvailableEvent.Reset();
}
}
}
private static void ExecuteJob(int jobSlot)
{
ref var job = ref _jobPool![jobSlot];
// Mark as running
if (Interlocked.CompareExchange(ref job.State, 1, 0) != 0)
return; // Job already taken by another thread
try
{
if (job.ExecuteJobFunction != null)
{
if (job.JobType == JobType.Job)
{
// Execute IJob
job.ExecuteJobFunction(job.JobDataObject!);
}
}
else if (job.ExecuteParallelJobFunction != null)
{
if (job.JobType == JobType.ParallelFor)
{
// Execute IJobParallelFor
ExecuteParallelJob(ref job);
}
}
}
finally
{
// Mark as completed
Volatile.Write(ref job.State, 2);
// Clean up additional dependencies
if (job.AdditionalDependencies != null)
{
var handle = AllocationManager.GetAllocationHandle(Allocator.Temp);
handle.Free(handle.Allocator, job.AdditionalDependencies);
job.AdditionalDependencies = null;
}
// Remove from active jobs and notify dependent jobs
_activeJobs.TryRemove(job.Id, out _);
NotifyDependentJobs(job.Id);
}
}
private static void ExecuteParallelJob(ref JobData job)
{
var batchSize = job.BatchSize > 0 ? job.BatchSize : Math.Max(1, job.TotalIterations / (_workerThreadCount * 4));
var currentIndex = 0;
var ranges = new JobRanges
{
JobIndex = 0,
BeginIndex = 0,
EndIndex = job.TotalIterations,
TotalLength = job.TotalIterations,
BatchSize = batchSize,
CurrentIndex = &currentIndex
};
var executeDelegate = job.ExecuteParallelJobFunction!;
var jobDataObject = job.JobDataObject!;
// Execute in parallel using available threads
Parallel.For(0, _workerThreadCount, threadIndex =>
{
executeDelegate(jobDataObject, ref ranges, threadIndex);
});
}
// TODO: Optimize by maintaining a reverse dependency graph
private static void NotifyDependentJobs(ulong completedJobId)
{
// Scan for jobs that depend on this completed job
for (var i = 0; i < _jobPool!.Count; i++)
{
ref var job = ref _jobPool[i];
if (job.State == 0 && job.DependencyCount > 0) // Scheduled and has dependencies
{
var isDependent = false;
// Check inline dependencies
for (var j = 0; j < Math.Min(job.DependencyCount, 8); j++)
{
if (job.Dependencies[j] == completedJobId)
{
isDependent = true;
break;
}
}
// Check additional dependencies
if (!isDependent && job.AdditionalDependencies != null)
{
for (var j = 0; j < job.AdditionalDependencyCount; j++)
{
if (job.AdditionalDependencies[j] == completedJobId)
{
isDependent = true;
break;
}
}
}
if (isDependent)
{
var completedCount = Interlocked.Increment(ref job.CompletedDependencies);
if (completedCount >= job.DependencyCount)
{
_readyJobs.Enqueue(i);
_workAvailableEvent.Set();
}
}
}
}
}
}

View File

@@ -0,0 +1,108 @@
namespace Misaki.HighPerformance.Jobs;
/// <summary>
/// Internal job struct for IJob execution, similar to Unity's JobStruct pattern.
/// This provides the bridge between the job scheduler and user job implementations.
/// </summary>
/// <typeparam name="T">The job type implementing IJob.</typeparam>
internal struct JobStruct<T> where T : class, IJob
{
/// <summary>
/// Cached function delegate for this job type.
/// This avoids allocations during job scheduling.
/// </summary>
internal static readonly ExecuteJobDelegate ExecuteDelegate;
static JobStruct()
{
// Create and cache the function delegate
ExecuteDelegate = Execute;
}
/// <summary>
/// Executes the job. This method matches the ExecuteJobDelegate signature.
/// </summary>
/// <param name="jobData">The job data object.</param>
public static void Execute(object jobData)
{
var typedJobData = (T)jobData;
typedJobData.Execute();
}
/// <summary>
/// Schedules this job type for execution.
/// </summary>
/// <param name="jobData">The job data.</param>
/// <param name="dependsOn">Job handle this job depends on.</param>
/// <returns>A job handle for the scheduled job.</returns>
public static JobHandle Schedule(T jobData, JobHandle dependsOn = default)
{
return JobScheduler.ScheduleJob(jobData, ExecuteDelegate, JobType.Job, 0, 0, dependsOn);
}
}
/// <summary>
/// Internal job struct for IJobParallelFor execution, similar to Unity's ParallelForJobStruct.
/// This provides efficient parallel execution with work stealing.
/// </summary>
/// <typeparam name="T">The job type implementing IJobParallelFor.</typeparam>
internal struct ParallelForJobStruct<T> where T : class, IJobParallelFor
{
/// <summary>
/// Cached function delegate for this job type.
/// </summary>
internal static readonly ExecuteParallelJobDelegate ExecuteDelegate;
static ParallelForJobStruct()
{
// Create and cache the function delegate
ExecuteDelegate = Execute;
}
/// <summary>
/// Executes the parallel job using work stealing. This method matches the ExecuteParallelJobDelegate signature.
/// </summary>
/// <param name="jobData">The job data object.</param>
/// <param name="ranges">Job ranges for work distribution.</param>
/// <param name="jobIndex">Index of the current worker thread.</param>
public static unsafe void Execute(object jobData, ref JobRanges ranges, int jobIndex)
{
var typedJobData = (T)jobData;
while (true)
{
if (!JobsUtility.GetWorkStealingRange(ref ranges, jobIndex, out var begin, out var end))
break;
// Execute the batch
var endThatCompilerCanSeeWillNeverChange = end;
for (var i = begin; i < endThatCompilerCanSeeWillNeverChange; ++i)
{
typedJobData.Execute(i);
}
}
}
/// <summary>
/// Schedules this parallel job type for execution.
/// </summary>
/// <param name="jobData">The job data.</param>
/// <param name="arrayLength">Total number of iterations.</param>
/// <param name="innerLoopBatchCount">Batch size for each worker. If <= 0, an optimal batch size will be calculated.</param>
/// <param name="dependsOn">Job handle this job depends on.</param>
/// <returns>A job handle for the scheduled job.</returns>
public static JobHandle ScheduleParallel(T jobData, int arrayLength, int innerLoopBatchCount = 0, JobHandle dependsOn = default)
{
if (arrayLength <= 0)
throw new ArgumentException("Array length must be greater than 0", nameof(arrayLength));
// Calculate optimal batch size if not specified
if (innerLoopBatchCount <= 0)
{
var workerCount = Environment.ProcessorCount;
innerLoopBatchCount = Math.Max(1, arrayLength / (workerCount * 4));
}
return JobScheduler.ScheduleParallelJob(jobData, ExecuteDelegate, arrayLength, innerLoopBatchCount, dependsOn);
}
}

View File

@@ -0,0 +1,41 @@
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Jobs;
/// <summary>
/// Utilities for job execution, similar to Unity's JobsUtility.
/// Provides low-level job management functions.
/// </summary>
internal static unsafe class JobsUtility
{
private static ulong s_nextJobId = 1;
/// <summary>
/// Gets the next unique job ID.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong GetNextJobId()
{
return (ulong)Interlocked.Increment(ref Unsafe.As<ulong, long>(ref s_nextJobId));
}
/// <summary>
/// Implements work stealing for parallel jobs.
/// Returns false when no more work is available.
/// </summary>
/// <param name="ranges">The job ranges containing work distribution information.</param>
/// <param name="jobIndex">The index of the current worker thread.</param>
/// <param name="beginIndex">Output: The starting index for this work batch.</param>
/// <param name="endIndex">Output: The ending index for this work batch.</param>
/// <returns>True if work was acquired, false if no more work is available.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetWorkStealingRange(ref JobRanges ranges, int jobIndex, out int beginIndex, out int endIndex)
{
var currentIndex = Interlocked.Add(ref *ranges.CurrentIndex, ranges.BatchSize);
beginIndex = currentIndex - ranges.BatchSize;
endIndex = Math.Min(currentIndex, ranges.TotalLength);
return beginIndex < ranges.TotalLength;
}
}

View File

@@ -1,9 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Misaki.HighPerformance.LowLevel\Misaki.HighPerformance.LowLevel.csproj" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,6 +0,0 @@
namespace Misaki.HighPerformance.Jobs;
internal static class WorkerThreadPool
{
private static readonly int _workerThreadCount = Environment.ProcessorCount;
}

View File

@@ -1,2 +1,6 @@
global using static Misaki.HighPerformance.LowLevel.Helpers.MemoryUtilities; global using static Misaki.HighPerformance.LowLevel.Helpers.MemoryUtilities;
global using SystemUnsfae = System.Runtime.CompilerServices.Unsafe;
global using unsafe AllocFunc = delegate* unmanaged<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, void*>;
global using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>;
global using unsafe ReallocFunc = delegate* unmanaged<void*, void*, nuint, nuint, void*>;

View File

@@ -1,22 +1,65 @@
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Exceptions; using Misaki.HighPerformance.LowLevel.Exceptions;
using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer; namespace Misaki.HighPerformance.LowLevel.Buffer;
using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>; /// <summary>
/// Holds information about a memory allocation.
/// </summary>
public readonly unsafe struct AllocationInfo
{
/// <summary>
/// Get the size of the allocation in bytes.
/// </summary>
public nuint Size
{
get; init;
}
public unsafe struct ArenaAllocator : IAllocator, IDisposable /// <summary>
/// Get the allocator used for the allocation.
/// </summary>
public void* Allocator
{
get; init;
}
/// <summary>
/// Get the function pointer used to free the allocated memory.
/// </summary>
public FreeFunc FreeHandler
{
get; init;
}
/// <summary>
/// Get the stack trace at the time of allocation for debugging purposes.
/// </summary>
public StackTrace StackTrace
{
get; init;
}
}
/// <summary>
/// Provides memory allocation management for native memory allocations, with support for tracking,
/// debugging, and custom allocation strategies.
/// </summary>
public static unsafe class AllocationManager
{
private unsafe struct ArenaAllocator : IAllocator, IDisposable
{ {
private DynamicArena _arena; private DynamicArena _arena;
private AllocationHandle _handle; private AllocationHandle _handle;
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle); public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public ArenaAllocator(uint initialSize) public void Init(uint initialSize)
{ {
_arena = new DynamicArena(initialSize); _arena = new DynamicArena(initialSize);
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock); _handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
@@ -37,13 +80,14 @@ public unsafe struct ArenaAllocator : IAllocator, IDisposable
var selfPtr = (ArenaAllocator*)instance; var selfPtr = (ArenaAllocator*)instance;
var newPtr = selfPtr->_arena.Allocate(size, alignment, AllocationOption.None); var newPtr = selfPtr->_arena.Allocate(size, alignment, AllocationOption.None);
MemCpy(newPtr, ptr, size); MemCpy(newPtr, ptr, size);
// NOTE: We do not free the old pointer here, as it is managed by the arena. // We do not free the old pointer here, as it is managed by the arena.
return newPtr; return newPtr;
} }
[UnmanagedCallersOnly] [UnmanagedCallersOnly]
private static void FreeBlock(void* instance, void* ptr) private static void FreeBlock(void* instance, void* ptr)
{ {
// The arena allocator does not free individual blocks, as it manages memory in chunks.
} }
public void Reset() public void Reset()
@@ -57,13 +101,13 @@ public unsafe struct ArenaAllocator : IAllocator, IDisposable
} }
} }
public unsafe struct DefaultAllocator : IAllocator private unsafe struct HeapAllocator : IAllocator
{ {
private AllocationHandle _handle; private AllocationHandle _handle;
public ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle); public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public DefaultAllocator() public void Init()
{ {
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock); _handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
} }
@@ -72,7 +116,12 @@ public unsafe struct DefaultAllocator : IAllocator
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption) private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
{ {
var ptr = AlignedAlloc(size, alignment); var ptr = AlignedAlloc(size, alignment);
AllocationManager.TrackAllocation(ptr, size, instance, &FreeBlock);
if (!allocationOption.HasFlag(AllocationOption.UnTracked))
{
TrackAllocation(ptr, size, instance, &FreeBlock);
}
if (allocationOption.HasFlag(AllocationOption.Clear)) if (allocationOption.HasFlag(AllocationOption.Clear))
{ {
MemClear(ptr, size); MemClear(ptr, size);
@@ -85,7 +134,7 @@ public unsafe struct DefaultAllocator : IAllocator
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment) private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
{ {
var newPtr = AlignedRealloc(ptr, size, alignment); var newPtr = AlignedRealloc(ptr, size, alignment);
AllocationManager.UpdateAllocation(ptr, newPtr, size, instance, &FreeBlock); UpdateAllocation(ptr, newPtr, size, instance, &FreeBlock);
return newPtr; return newPtr;
} }
@@ -93,98 +142,72 @@ public unsafe struct DefaultAllocator : IAllocator
private static void FreeBlock(void* instance, void* ptr) private static void FreeBlock(void* instance, void* ptr)
{ {
AlignedFree(ptr); AlignedFree(ptr);
AllocationManager.RemoveAllocation(ptr); UntrackAllocation(ptr);
}
}
public unsafe struct EmptyAllocator : IAllocator
{
private AllocationHandle _handle;
public ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public EmptyAllocator()
{
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
}
[UnmanagedCallersOnly]
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
{
return null;
}
[UnmanagedCallersOnly]
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
{
return ptr;
}
[UnmanagedCallersOnly]
private static void FreeBlock(void* instance, void* ptr)
{
}
}
public static unsafe class AllocationManager
{
public readonly struct AllocationInfo
{
public nuint Size
{
get; init;
}
public void* Allocator
{
get; init;
}
public FreeFunc FreeHandler
{
get; init;
}
public StackTrace StackTrace
{
get; init;
} }
} }
private const uint _DEFAULT_ARENA_SIZE = 512 * 1024; private const uint _DEFAULT_ARENA_SIZE = 512 * 1024;
private static ArenaAllocator s_arenaAllocator = new(_DEFAULT_ARENA_SIZE); private static readonly ArenaAllocator* s_arenaAllocator;
private static DefaultAllocator s_persistentAllocator = new(); private static readonly HeapAllocator* s_persistentAllocator;
private static EmptyAllocator s_emptyAllocator = new();
private static bool s_debugLayer; private static bool s_debugLayer;
private static Dictionary<nint, AllocationInfo>? s_allocated; private static ConcurrentDictionary<nint, AllocationInfo>? s_allocated;
public static ArenaAllocator TempAllocator => s_arenaAllocator; /// <summary>
public static DefaultAllocator PersistentAllocator => s_persistentAllocator; /// Gets a reference to the allocation handle for temporary allocations.
public static EmptyAllocator EmptyAllocator => s_emptyAllocator; /// </summary>
public static ref AllocationHandle TempHandle => ref s_arenaAllocator->Handle;
/// <summary>
/// Gets a reference to the persistent allocation handle.
/// </summary>
public static ref AllocationHandle PersistentHandle => ref s_persistentAllocator->Handle;
static AllocationManager()
{
s_arenaAllocator = (ArenaAllocator*)NativeMemory.Alloc((nuint)sizeof(ArenaAllocator));
s_persistentAllocator = (HeapAllocator*)NativeMemory.Alloc((nuint)sizeof(HeapAllocator));
s_arenaAllocator->Init(_DEFAULT_ARENA_SIZE);
s_persistentAllocator->Init();
}
/// <summary>
/// Enables the debug layer, allowing additional diagnostic information to be collected.
/// </summary>
public static void EnableDebugLayer() public static void EnableDebugLayer()
{ {
s_debugLayer = true; s_debugLayer = true;
s_allocated ??= new Dictionary<nint, AllocationInfo>(64); s_allocated ??= new(-1, 64);
} }
/// <summary>
/// Gets a reference to the allocation handle for the specified allocator type.
/// </summary>
/// <param name="allocator">The allocator type for which to retrieve the allocation handle.</param>
/// <returns>A reference to the allocation handle associated with the specified allocator type.</returns>
/// <exception cref="ArgumentException"></exception>
public static ref AllocationHandle GetAllocationHandle(Allocator allocator) public static ref AllocationHandle GetAllocationHandle(Allocator allocator)
{ {
switch (allocator) switch (allocator)
{ {
case Allocator.Temp: case Allocator.Temp:
return ref s_arenaAllocator.Handle; return ref TempHandle;
case Allocator.Persistent: case Allocator.Persistent:
return ref s_persistentAllocator.Handle; return ref PersistentHandle;
case Allocator.External:
return ref s_emptyAllocator.Handle;
default: default:
throw new ArgumentException("Invalid allocator type.", nameof(allocator)); throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator));
} }
} }
/// <summary>
/// Tracks a memory allocation in the allocation manager.
/// </summary>
/// <param name="ptr">The pointer to the allocated memory.</param>
/// <param name="allocationSize">The size of the allocation in bytes.</param>
/// <param name="allocator">The allocator used for the allocation.</param>
/// <param name="freeFunc">The function pointer used to free the allocated memory.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void TrackAllocation(void* ptr, nuint allocationSize, void* allocator, FreeFunc freeFunc) public static void TrackAllocation(void* ptr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
{ {
@@ -202,6 +225,14 @@ public static unsafe class AllocationManager
}; };
} }
/// <summary>
/// Updates the allocation tracking information by replacing the old pointer with a new pointer.
/// </summary>
/// <param name="oldPtr">A pointer to the previously allocated memory. If <paramref name="oldPtr"/> is not tracked, the method does nothing.</param>
/// <param name="newPtr">A pointer to the newly allocated memory. This pointer will replace <paramref name="oldPtr"/> in the allocation tracking.</param>
/// <param name="allocationSize">The size, in bytes, of the new allocation.</param>
/// <param name="allocator">A pointer to the allocator responsible for the new allocation.</param>
/// <param name="freeFunc">A delegate or function pointer used to free the memory associated with the allocation.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UpdateAllocation(void* oldPtr, void* newPtr, nuint allocationSize, void* allocator, FreeFunc freeFunc) public static void UpdateAllocation(void* oldPtr, void* newPtr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
{ {
@@ -210,6 +241,7 @@ public static unsafe class AllocationManager
return; return;
} }
// If we don't find the allocation info, that means the oldPtr was not tracked
if (s_allocated.Remove((nint)oldPtr, out var info)) if (s_allocated.Remove((nint)oldPtr, out var info))
{ {
s_allocated[(nint)newPtr] = new AllocationInfo s_allocated[(nint)newPtr] = new AllocationInfo
@@ -220,20 +252,20 @@ public static unsafe class AllocationManager
StackTrace = info.StackTrace StackTrace = info.StackTrace
}; };
} }
else
{
TrackAllocation(newPtr, allocationSize, allocator, freeFunc);
}
} }
public static void RemoveAllocation(void* ptr) /// <summary>
/// Removes the specified memory allocation from the tracking system.
/// </summary>
/// <param name="ptr">A pointer to the memory allocation to untrack.</param>
public static void UntrackAllocation(void* ptr)
{ {
if (s_allocated == null) if (s_allocated == null)
{ {
return; return;
} }
s_allocated.Remove((nint)ptr); s_allocated.Remove((nint)ptr, out _);
} }
/// <summary> /// <summary>
@@ -241,8 +273,6 @@ public static unsafe class AllocationManager
/// </summary> /// </summary>
public static void Dispose() public static void Dispose()
{ {
s_arenaAllocator.Dispose();
if (s_allocated != null) if (s_allocated != null)
{ {
nuint unfreeBytes = 0u; nuint unfreeBytes = 0u;
@@ -259,5 +289,16 @@ public static unsafe class AllocationManager
s_allocated.Clear(); s_allocated.Clear();
} }
if (s_arenaAllocator != null)
{
s_arenaAllocator->Dispose();
NativeMemory.Free(s_arenaAllocator);
}
if (s_persistentAllocator != null)
{
NativeMemory.Free(s_persistentAllocator);
}
} }
} }

View File

@@ -1,4 +1,4 @@
namespace Misaki.HighPerformance.LowLevel.Collections; namespace Misaki.HighPerformance.LowLevel.Buffer;
[Flags] [Flags]
public enum AllocationOption : byte public enum AllocationOption : byte
@@ -9,8 +9,7 @@ public enum AllocationOption : byte
/// </summary> /// </summary>
Clear = 1 << 0, Clear = 1 << 0,
/// <summary> /// <summary>
/// Allocator for untracked memory. It always allocates memory without using the allocation manager. /// Allocator for untracked memory.
/// Always free it manually even if you use the <see cref="Allocator.Temp"/> allocator.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Use this option carefully, as the allocation manager will not track the memory. /// Use this option carefully, as the allocation manager will not track the memory.
@@ -30,9 +29,5 @@ public enum Allocator : byte
/// <summary> /// <summary>
/// Allocator for persistent allocations. Allocations are not cleared after use. /// Allocator for persistent allocations. Allocations are not cleared after use.
/// </summary> /// </summary>
Persistent, Persistent
/// <summary>
/// Allocator for external memory. Allocations are not cleared after use.
/// </summary>
External
} }

View File

@@ -1,18 +1,20 @@
using Misaki.HighPerformance.LowLevel.Collections; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer; namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary> /// <summary>
/// A memory management structure that allocates and resets memory blocks with specified alignment. /// A memory management structure that allocates and resets memory blocks with specified alignment.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Explicit, Size = 64)] // Cache line aligned to prevent false sharing
public unsafe struct Arena : IDisposable public unsafe struct Arena : IDisposable
{ {
[FieldOffset(0)]
private byte* _buffer; private byte* _buffer;
[FieldOffset(8)]
private nuint _size; private nuint _size;
[FieldOffset(16)]
private nuint _offset; private nuint _offset;
private bool _disposed;
public Arena(nuint size) public Arena(nuint size)
{ {
Initialize(size); Initialize(size);
@@ -42,20 +44,36 @@ public unsafe struct Arena : IDisposable
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception> /// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption) public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
{ {
if (_disposed) if (_buffer == null)
{ {
throw new ObjectDisposedException(nameof(DynamicArena)); throw new ObjectDisposedException(nameof(DynamicArena));
} }
var offset = _offset + alignment - 1 & ~(alignment - 1); //var offset = _offset + alignment - 1 & ~(alignment - 1);
if (offset + size > _size) //if (offset + size > _size)
//{
// return null;
//}
//_offset = offset + size;
//var ptr = _buffer + offset;
nuint currentOffset, newOffset, alignedOffset;
do
{
currentOffset = _offset;
alignedOffset = (currentOffset + alignment - 1) & ~(alignment - 1);
newOffset = alignedOffset + size;
if (newOffset > _size)
{ {
return null; return null;
} }
_offset = offset + size; } while (Interlocked.CompareExchange(ref _offset, newOffset, currentOffset) != currentOffset);
var ptr = _buffer + offset;
var ptr = _buffer + alignedOffset;
if (allocationOption.HasFlag(AllocationOption.Clear)) if (allocationOption.HasFlag(AllocationOption.Clear))
{ {
MemClear(ptr, size); MemClear(ptr, size);
@@ -71,7 +89,7 @@ public unsafe struct Arena : IDisposable
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception> /// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
public void Reset() public void Reset()
{ {
if (_disposed) if (_buffer == null)
{ {
throw new ObjectDisposedException(nameof(DynamicArena)); throw new ObjectDisposedException(nameof(DynamicArena));
} }
@@ -81,12 +99,15 @@ public unsafe struct Arena : IDisposable
public void Dispose() public void Dispose()
{ {
if (_buffer == null)
{
return;
}
Free(_buffer); Free(_buffer);
_buffer = null; _buffer = null;
_size = 0; _size = 0;
_offset = 0; _offset = 0;
_disposed = true;
} }
} }

View File

@@ -1,4 +1,4 @@
using Misaki.HighPerformance.LowLevel.Collections; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer; namespace Misaki.HighPerformance.LowLevel.Buffer;
@@ -6,29 +6,33 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
/// A dynamic memory management structure that automatically grows by creating linked arenas /// A dynamic memory management structure that automatically grows by creating linked arenas
/// when more space is needed. /// when more space is needed.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Explicit, Size = 128)]
public unsafe struct DynamicArena : IDisposable public unsafe struct DynamicArena : IDisposable
{ {
[StructLayout(LayoutKind.Sequential)]
private struct ArenaNode private struct ArenaNode
{ {
public Arena arena; public Arena arena;
public ArenaNode* next; public ArenaNode* next;
} }
[FieldOffset(0)]
private ArenaNode* _root; private ArenaNode* _root;
[FieldOffset(8)]
private ArenaNode* _current; private ArenaNode* _current;
[FieldOffset(16)]
private uint _initialSize; private uint _initialSize;
[FieldOffset(20)]
private volatile int _nodeCreationLock;
/// <summary> /// <summary>
/// Initializes a new instance of DynamicArena with the specified initial size. /// Initializes a new instance of DynamicArena with the specified initial size.
/// </summary> /// </summary>
/// <param name="initialSize">The initial size in bytes for the first arena block.</param> /// <param name="initialSize">The initial size in bytes for the first arena block.</param>
public DynamicArena(uint initialSize) public DynamicArena(uint initialSize)
{ {
_initialSize = initialSize; Initialize(initialSize);
_root = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
_root->arena = new Arena(initialSize);
_root->next = null;
_current = _root;
} }
public void Initialize(uint initialSize) public void Initialize(uint initialSize)
@@ -45,15 +49,33 @@ public unsafe struct DynamicArena : IDisposable
_current = _root; _current = _root;
} }
private bool CreateNewNode(nuint size) private bool TryCreateNewNode(nuint size)
{ {
while (Interlocked.CompareExchange(ref _nodeCreationLock, 1, 0) != 0)
{
Thread.SpinWait(1);
}
try
{
var current = _current;
if (current->next != null)
{
// Another thread created a node while we were waiting
_current = current->next;
return true;
}
var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>()); var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
try try
{ {
newNode->arena = new Arena(size); newNode->arena = new Arena(size);
newNode->next = null; newNode->next = null;
_current->next = newNode; // Atomically link the new node
current->next = newNode;
// Update current pointer
_current = newNode; _current = newNode;
return true; return true;
} }
@@ -63,6 +85,28 @@ public unsafe struct DynamicArena : IDisposable
return false; return false;
} }
} }
finally
{
// Release the spinlock
Interlocked.Exchange(ref _nodeCreationLock, 0);
}
//var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
//try
//{
// newNode->arena = new Arena(size);
// newNode->next = null;
// _current->next = newNode;
// _current = newNode;
// return true;
//}
//catch
//{
// Free(newNode);
// return false;
//}
}
/// <summary> /// <summary>
/// Allocates a block of memory with specified size and alignment. Creates a new arena if current one is full. /// Allocates a block of memory with specified size and alignment. Creates a new arena if current one is full.
@@ -89,7 +133,7 @@ public unsafe struct DynamicArena : IDisposable
return result; return result;
} }
if (current->next == null && !CreateNewNode(Math.Max(size, _initialSize))) if (current->next == null && !TryCreateNewNode(Math.Max(size, _initialSize)))
{ {
return null; return null;
} }

View File

@@ -86,7 +86,7 @@ public unsafe struct FixedStackString32
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length); Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
} }
} }
@@ -201,7 +201,7 @@ public unsafe struct FixedStackString64
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length); Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
} }
} }
@@ -316,7 +316,7 @@ public unsafe struct FixedStackString128
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length); Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
} }
} }
@@ -431,7 +431,7 @@ public unsafe struct FixedStackString256
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length); Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
} }
} }
@@ -546,7 +546,7 @@ public unsafe struct FixedStackString512
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length); Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
} }
} }
@@ -661,7 +661,7 @@ public unsafe struct FixedStackString1024
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length); Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
} }
} }
@@ -776,7 +776,7 @@ public unsafe struct FixedStackString2048
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length); Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
} }
} }
@@ -891,7 +891,7 @@ public unsafe struct FixedStackString4096
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length); Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
} }
} }

View File

@@ -93,7 +93,7 @@ public unsafe struct FixedStackString<#= i #>
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer) fixed (byte* bufferPtr = _buffer)
{ {
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length); Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
} }
} }

View File

@@ -77,7 +77,7 @@ public unsafe struct FixedString32 : IDisposable
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
{ {
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length); Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
} }
} }
@@ -188,7 +188,7 @@ public unsafe struct FixedString64 : IDisposable
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
{ {
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length); Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
} }
} }
@@ -299,7 +299,7 @@ public unsafe struct FixedString128 : IDisposable
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
{ {
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length); Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
} }
} }
@@ -410,7 +410,7 @@ public unsafe struct FixedString256 : IDisposable
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
{ {
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length); Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
} }
} }
@@ -521,7 +521,7 @@ public unsafe struct FixedString512 : IDisposable
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
{ {
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length); Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
} }
} }
@@ -632,7 +632,7 @@ public unsafe struct FixedString1024 : IDisposable
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
{ {
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length); Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
} }
} }
@@ -743,7 +743,7 @@ public unsafe struct FixedString2048 : IDisposable
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
{ {
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length); Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
} }
} }
@@ -854,7 +854,7 @@ public unsafe struct FixedString4096 : IDisposable
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
{ {
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length); Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
} }
} }

View File

@@ -84,7 +84,7 @@ public unsafe struct FixedString<#= i #> : IDisposable
fixed (byte* inputPtr = input) fixed (byte* inputPtr = input)
{ {
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length); Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
} }
} }

View File

@@ -0,0 +1,479 @@
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// A lock-free, thread-safe variable-size allocator that manages memory blocks of different sizes.
/// Optimized for high-performance scenarios with frequent allocations and deallocations.
///
/// Example usage:
/// <code>
/// // Create a free list with multiple size buckets
/// var freeList = new FreeList();
///
/// // Allocate a 70-byte block
/// var block = freeList.Allocate(70);
/// if (block.IsValid)
/// {
/// // Use the memory block...
///
/// // Free the block when done
/// freeList.Free(block);
/// }
///
/// // Dispose when finished
/// freeList.Dispose();
/// </code>
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 256)] // Cache line aligned to prevent false sharing
public unsafe struct FreeList : IDisposable
{
/// <summary>
/// Node structure for the lock-free free list with size information.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct FreeNode
{
public FreeNode* next;
public nuint size;
}
/// <summary>
/// Memory chunk that contains variable-size blocks.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct MemoryChunk
{
public MemoryChunk* next;
public byte* memory;
public nuint size;
public nuint used; // Amount of memory used in this chunk
}
/// <summary>
/// Size bucket for different allocation sizes.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct SizeBucket
{
public nint freeHead; // Free list head for this size
public nuint blockSize; // Fixed size for this bucket
public long freeCount; // Number of free blocks
}
private const int _MAX_BUCKETS = 16; // Number of size buckets
private const nuint _MIN_BLOCK_SIZE = 16; // Minimum block size
private const nuint _DEFAULT_CHUNK_SIZE = 64 * 1024; // 64KB chunks
[FieldOffset(0)]
private fixed byte _buckets[_MAX_BUCKETS * 32]; // SizeBucket array (32 bytes per bucket)
[FieldOffset(512)]
private DynamicArena _chunkArena; // 128
[FieldOffset(640)]
private MemoryChunk* _chunks; // 8
[FieldOffset(648)]
private nuint _chunkSize; // 8
[FieldOffset(656)]
private nuint _alignment; // 8
[FieldOffset(664)]
private long _totalAllocatedBytes; // 8
[FieldOffset(672)]
private long _totalFreeBytes; // 8
[FieldOffset(676)]
private volatile int _disposed; // 4
[FieldOffset(680)]
private volatile int _chunkCreationLock; // 4
/// <summary>
/// Gets the alignment requirement for allocations.
/// </summary>
public readonly nuint Alignment => _alignment;
/// <summary>
/// Gets the total number of allocated bytes.
/// </summary>
public readonly long TotalAllocatedBytes => Interlocked.Read(ref Unsafe.AsRef(in _totalAllocatedBytes));
/// <summary>
/// Gets the total number of free bytes available.
/// </summary>
public readonly long TotalFreeBytes => Interlocked.Read(ref Unsafe.AsRef(in _totalFreeBytes));
/// <summary>
/// Gets whether the allocator has been disposed.
/// </summary>
public readonly bool IsDisposed => _disposed != 0;
/// <summary>
/// Gets the chunk size used by this allocator.
/// </summary>
public readonly nuint ChunkSize => _chunkSize;
/// <summary>
/// Initializes a new variable-size FreeList allocator with the specified parameters.
/// </summary>
/// <param name="alignment">Alignment requirement for blocks (must be power of 2).</param>
/// <param name="chunkSize">Size of memory chunks to allocate (default: 64KB).</param>
public FreeList(nuint alignment = 8, nuint chunkSize = _DEFAULT_CHUNK_SIZE)
{
if (alignment == 0 || (alignment & (alignment - 1)) != 0)
throw new ArgumentException("Alignment must be a power of 2", nameof(alignment));
if (chunkSize < 1024)
throw new ArgumentException("Chunk size must be at least 1KB", nameof(chunkSize));
_alignment = alignment;
_chunkSize = chunkSize;
_chunks = null;
_totalAllocatedBytes = 0;
_totalFreeBytes = 0;
_disposed = 0;
_chunkCreationLock = 0;
_chunkArena = new DynamicArena(1024);
InitializeBuckets();
}
/// <summary>
/// Initializes the size buckets with exponential sizes.
/// </summary>
private readonly void InitializeBuckets()
{
var buckets = GetBuckets();
var size = _MIN_BLOCK_SIZE;
for (var i = 0; i < _MAX_BUCKETS; i++)
{
buckets[i].blockSize = size;
buckets[i].freeHead = 0;
buckets[i].freeCount = 0;
size *= 2; // Exponential size increase
}
}
/// <summary>
/// Gets a pointer to the size buckets array.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly SizeBucket* GetBuckets()
{
fixed (byte* ptr = _buckets)
{
return (SizeBucket*)ptr;
}
}
/// <summary>
/// Finds the appropriate bucket for the given size.
/// </summary>
/// <param name="size">Size to find bucket for.</param>
/// <returns>Bucket index, or -1 if too large for buckets.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly int FindBucket(nuint size)
{
var buckets = GetBuckets();
for (var i = 0; i < _MAX_BUCKETS; i++)
{
if (size <= buckets[i].blockSize)
return i;
}
return -1; // Size too large for buckets
}
/// <summary>
/// Allocates a memory block of the specified size. Thread-safe using lock-free algorithms.
/// </summary>
/// <param name="size">Size of memory to allocate in bytes.</param>
/// <param name="alignment">Alignment requirement (0 = use default).</param>
/// <param name="allocationOption">Options for allocation (e.g., clear memory).</param>
/// <returns>MemoryBlock containing allocated memory, or Invalid if allocation fails.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MemoryBlock Allocate(nuint size, nuint alignment = 0, AllocationOption allocationOption = AllocationOption.None)
{
if (_disposed != 0 || size == 0)
return MemoryBlock.Invalid;
if (alignment == 0)
alignment = _alignment;
// Align size to alignment boundary
var alignedSize = (size + alignment - 1) & ~(alignment - 1);
alignedSize = Math.Max(alignedSize, _MIN_BLOCK_SIZE);
var bucketIndex = FindBucket(alignedSize);
void* ptr = null;
if (bucketIndex >= 0)
{
// Try to allocate from bucket
ptr = TryPopFromBucket(bucketIndex);
if (ptr == null)
{
// Create new blocks for this bucket
if (TryCreateBlocksForBucket(bucketIndex))
{
ptr = TryPopFromBucket(bucketIndex);
}
}
}
if (ptr == null)
{
// Fallback to direct allocation from chunk
ptr = AllocateFromChunk(alignedSize, alignment);
}
if (ptr != null)
{
Interlocked.Add(ref _totalAllocatedBytes, (long)alignedSize);
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(ptr, alignedSize);
}
return new MemoryBlock(ptr, alignedSize, alignment);
}
return MemoryBlock.Invalid;
}
/// <summary>
/// Frees a previously allocated memory block. Thread-safe using lock-free algorithms.
/// </summary>
/// <param name="block">MemoryBlock to free.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Free(MemoryBlock block)
{
if (!block.IsValid || _disposed != 0)
return;
if (!IsValidBlock(block.Ptr))
return; // Invalid pointer, ignore
var bucketIndex = FindBucket(block.Size);
if (bucketIndex >= 0)
{
PushToBucket(bucketIndex, block.Ptr, block.Size);
}
Interlocked.Add(ref _totalAllocatedBytes, -(long)block.Size);
}
/// <summary>
/// Tries to pop a free block from the specified bucket.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly void* TryPopFromBucket(int bucketIndex)
{
var buckets = GetBuckets();
var bucket = &buckets[bucketIndex];
nint head, newHead;
FreeNode* headPtr;
do
{
head = bucket->freeHead;
if (head == 0)
return null;
headPtr = (FreeNode*)head;
newHead = (nint)headPtr->next;
} while (Interlocked.CompareExchange(ref bucket->freeHead, newHead, head) != head);
Interlocked.Decrement(ref bucket->freeCount);
return (void*)head;
}
/// <summary>
/// Pushes a block to the specified bucket's free list.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly void PushToBucket(int bucketIndex, void* ptr, nuint size)
{
var buckets = GetBuckets();
var bucket = &buckets[bucketIndex];
var node = (FreeNode*)ptr;
node->size = size;
nint head;
do
{
head = bucket->freeHead;
node->next = (FreeNode*)head;
} while (Interlocked.CompareExchange(ref bucket->freeHead, (nint)node, head) != head);
Interlocked.Increment(ref bucket->freeCount);
}
/// <summary>
/// Creates new blocks for the specified bucket.
/// </summary>
private bool TryCreateBlocksForBucket(int bucketIndex)
{
while (Interlocked.CompareExchange(ref _chunkCreationLock, 1, 0) != 0)
{
Thread.SpinWait(1);
}
try
{
var buckets = GetBuckets();
var blockSize = buckets[bucketIndex].blockSize;
var blocksToCreate = Math.Min(_chunkSize / blockSize, 256); // Limit number of blocks
if (blocksToCreate == 0)
return false;
var totalSize = blocksToCreate * blockSize;
var memory = (byte*)AlignedAlloc(totalSize, _alignment);
if (memory == null)
return false;
var chunk = (MemoryChunk*)_chunkArena.Allocate(SizeOf<MemoryChunk>(), AlignOf<MemoryChunk>(), AllocationOption.None);
if (chunk == null)
{
AlignedFree(memory);
return false;
}
chunk->memory = memory;
chunk->size = totalSize;
chunk->used = totalSize;
chunk->next = _chunks;
_chunks = chunk;
// Add all blocks to the bucket's free list
for (nuint i = 0; i < blocksToCreate; i++)
{
var blockPtr = memory + (i * blockSize);
PushToBucket(bucketIndex, blockPtr, blockSize);
}
return true;
}
finally
{
Interlocked.Exchange(ref _chunkCreationLock, 0);
}
}
/// <summary>
/// Allocates memory directly from a chunk (for large allocations).
/// </summary>
private void* AllocateFromChunk(nuint size, nuint alignment)
{
while (Interlocked.CompareExchange(ref _chunkCreationLock, 1, 0) != 0)
{
Thread.SpinWait(1);
}
try
{
// Try to find space in existing chunks first
var chunk = _chunks;
while (chunk != null)
{
var available = chunk->size - chunk->used;
var alignedOffset = (chunk->used + alignment - 1) & ~(alignment - 1);
var totalNeeded = alignedOffset - chunk->used + size;
if (totalNeeded <= available)
{
var ptr = chunk->memory + alignedOffset;
chunk->used += totalNeeded;
return ptr;
}
chunk = chunk->next;
}
// Create new chunk
var newChunkSize = Math.Max(_chunkSize, size + alignment);
var newMemory = (byte*)AlignedAlloc(newChunkSize, alignment);
if (newMemory == null)
return null;
var newChunk = (MemoryChunk*)_chunkArena.Allocate(SizeOf<MemoryChunk>(), AlignOf<MemoryChunk>(), AllocationOption.None);
if (newChunk == null)
{
AlignedFree(newMemory);
return null;
}
newChunk->memory = newMemory;
newChunk->size = newChunkSize;
newChunk->used = size;
newChunk->next = _chunks;
_chunks = newChunk;
return newMemory;
}
finally
{
Interlocked.Exchange(ref _chunkCreationLock, 0);
}
}
/// <summary>
/// Validates that a pointer belongs to one of our memory chunks.
/// </summary>
private readonly bool IsValidBlock(void* ptr)
{
var chunk = _chunks;
while (chunk != null)
{
var chunkStart = (nuint)chunk->memory;
var chunkEnd = chunkStart + chunk->size;
var ptrValue = (nuint)ptr;
if (ptrValue >= chunkStart && ptrValue < chunkEnd)
return true;
chunk = chunk->next;
}
return false;
}
/// <summary>
/// Disposes the free list and frees all allocated memory.
/// Note: This method is NOT thread-safe by design as requested.
/// </summary>
public void Dispose()
{
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0)
{
// Free all memory chunks
var chunk = _chunks;
while (chunk != null)
{
var next = chunk->next;
AlignedFree(chunk->memory);
MemoryUtilities.Free(chunk);
chunk = next;
}
_chunks = null;
_totalAllocatedBytes = 0;
_totalFreeBytes = 0;
}
}
}

View File

@@ -0,0 +1,68 @@
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// Represents an allocated memory block with metadata.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public unsafe readonly struct MemoryBlock
{
/// <summary>
/// Pointer to the actual allocated memory.
/// </summary>
public void* Ptr
{
get;
}
/// <summary>
/// Size of the allocated memory in bytes.
/// </summary>
public nuint Size
{
get;
}
/// <summary>
/// Alignment of the allocated memory.
/// </summary>
public nuint Alignment
{
get;
}
/// <summary>
/// Indicates whether this memory block is valid.
/// </summary>
public readonly bool IsValid => Ptr != null && Size > 0;
/// <summary>
/// Creates a new MemoryBlock with the specified parameters.
/// </summary>
/// <param name="ptr">Pointer to the allocated memory.</param>
/// <param name="size">Size of the allocated memory.</param>
/// <param name="alignment">Alignment of the allocated memory.</param>
public MemoryBlock(void* ptr, nuint size, nuint alignment)
{
Ptr = ptr;
Size = size;
Alignment = alignment;
}
/// <summary>
/// Creates an invalid MemoryBlock.
/// </summary>
public static MemoryBlock Invalid => new(null, 0, 0);
public Span<T> AsSpan<T>()
where T : unmanaged
{
if (!IsValid)
{
throw new InvalidOperationException("Cannot create span from invalid MemoryBlock.");
}
return new Span<T>(Ptr, (int)(Size / SizeOf<T>()));
}
}

View File

@@ -1,19 +0,0 @@
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.LowLevel.Buffer;
// TODO: Implement a pool for UnsafeArray<T>.
public unsafe static class UnsafeArrayPool
{
public static UnsafeArray<T> Rent<T>(int minimalSize)
where T : unmanaged
{
throw new NotImplementedException();
}
public static void Return<T>(UnsafeArray<T> array)
where T : unmanaged
{
throw new NotImplementedException();
}
}

View File

@@ -1,16 +1,7 @@
namespace Misaki.HighPerformance.LowLevel.Collections.Contracts; namespace Misaki.HighPerformance.LowLevel.Collections.Contracts;
public unsafe interface IUnsafeCollection<T> : IEnumerable<T>, IDisposable public unsafe interface IUnsafeCollection : IDisposable
where T : unmanaged
{ {
/// <summary>
/// Gets the number of elements in a collection. The value is read-only.
/// </summary>
public int Count
{
get;
}
/// <summary> /// <summary>
/// Indicates whether the object has been created. Returns true if the object is created, otherwise false. /// Indicates whether the object has been created. Returns true if the object is created, otherwise false.
/// </summary> /// </summary>
@@ -24,15 +15,41 @@ public unsafe interface IUnsafeCollection<T> : IEnumerable<T>, IDisposable
/// </summary> /// </summary>
public void Clear(); public void Clear();
/// <summary>
/// Changes the size of a collection or array to the specified value.
/// </summary>
/// <param name="newSize">Specifies the new size to which the collection or array should be adjusted.</param>
public void Resize(int newSize);
/// <summary> /// <summary>
/// Returns a pointer to an unmanaged memory location. This pointer can be used for low-level memory operations. /// Returns a pointer to an unmanaged memory location. This pointer can be used for low-level memory operations.
/// </summary> /// </summary>
/// <returns>The method returns a void pointer to the unsafe memory location.</returns> /// <returns>The method returns a void pointer to the unsafe memory location.</returns>
public void* GetUnsafePtr(); public void* GetUnsafePtr();
} }
public unsafe interface IUnsafeCollection<T> : IUnsafeCollection, IEnumerable<T>
where T : unmanaged
{
/// <summary>
/// Gets the number of elements in a collection. The value is read-only.
/// </summary>
public int Count
{
get;
}
/// <summary>
/// Changes the size of a collection to the specified value.
/// </summary>
/// <param name="newSize">Specifies the new size to which the collection should be adjusted.</param>
public void Resize(int newSize);
}
public unsafe interface IUnTypedCollection : IUnsafeCollection
{
/// <summary>
/// The total size of the buffer in bytes.
/// </summary>
public uint Size
{
get;
}
public ref T GetElementAt<T>(uint index)
where T : unmanaged;
}

View File

@@ -0,0 +1,152 @@
using System.Collections;
namespace Misaki.HighPerformance.LowLevel.Collections;
public class SlotMap<T> : IEnumerable<T>
{
public struct Enumerator : IEnumerator<T>
{
private readonly SlotMap<T> _slotMap;
private int _currentIndex;
public Enumerator(SlotMap<T> slotMap)
{
_slotMap = slotMap;
_currentIndex = -1;
}
public readonly T Current => _slotMap._data[_currentIndex].value;
readonly object? IEnumerator.Current => Current;
public bool MoveNext()
{
while (++_currentIndex < _slotMap._capacity)
{
if (_slotMap._data[_currentIndex].isValid)
{
return true;
}
}
return false;
}
public void Reset() => _currentIndex = -1;
public void Dispose()
{
}
}
private struct SlotData
{
public T value;
public bool isValid;
}
private SlotData[] _data;
private readonly Queue<int> _freeSlots;
private int _count;
private int _capacity;
public int Count => _count;
public int Capacity => _capacity;
public ref T this[int slotIndex]
{
get
{
if (slotIndex < 0 || slotIndex >= _capacity)
{
throw new ArgumentOutOfRangeException(nameof(slotIndex), "Slot index is out of range.");
}
ref var slot = ref _data[slotIndex];
if (!slot.isValid)
{
throw new InvalidOperationException($"Slot {slotIndex} is not occupied.");
}
return ref slot.value;
}
}
public IEnumerator<T> GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public SlotMap(int initialCapacity = 16)
{
_capacity = initialCapacity;
_data = new SlotData[initialCapacity];
_freeSlots = new Queue<int>(initialCapacity);
}
private void Resize()
{
var newCapacity = _capacity * 2;
Array.Resize(ref _data, newCapacity);
_freeSlots.EnsureCapacity(newCapacity);
_capacity = newCapacity;
}
public int Add(T item)
{
if (_count >= _capacity)
{
Resize();
}
int slotIndex;
if (_freeSlots.Count == 0)
{
slotIndex = _count;
}
else
{
slotIndex = _freeSlots.Dequeue();
}
ref var slot = ref _data[slotIndex];
slot.value = item;
slot.isValid = true;
_count++;
return slotIndex;
}
public bool Remove(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= _capacity)
{
return false;
}
ref var slot = ref _data[slotIndex];
if (!slot.isValid)
{
return false;
}
slot.isValid = false;
_freeSlots.Enqueue(slotIndex);
_count--;
return true;
}
public void Clear()
{
_count = 0;
_data.AsSpan().Clear();
_freeSlots.Clear();
}
}

View File

@@ -0,0 +1,125 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
public unsafe struct UnTypedArray : IUnTypedCollection
{
private void* _buffer;
private uint _size;
private uint _alignment;
private AllocationHandle* _handle;
public readonly uint Size => _size;
public readonly uint Alignment => _alignment;
public readonly bool IsCreated
{
get => _buffer != null;
}
/// <summary>
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
/// </summary>
public UnTypedArray()
: this(1, 1, Allocator.Persistent)
{
}
public UnTypedArray(uint size, uint alignment, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (size <= 0)
{
throw new ArgumentOutOfRangeException(nameof(size), "Count must be greater than zero.");
}
_handle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
_buffer = handle.Alloc(_handle->Allocator, size, alignment, allocationOption);
_size = size;
_alignment = alignment;
}
/// <summary>
/// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation type.
/// </summary>
/// <param name="count">Specifies the number of elements to allocate in the array, which must be greater than zero.</param>
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
public UnTypedArray(uint size, uint alignment, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(size, alignment, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Initializes an UnsafeArray with a pointer to a buffer and a count of elements. This does not copy the data.
/// </summary>
/// <param name="buffer">A pointer to the memory location that holds the elements of the array.</param>
/// <param name="count">The total size of the data.</param>
/// <remarks>
/// When using this constructor, the user is responsible for managing the memory pointed to by the buffer.
/// Disposing of the UnsafeArray does not free the memory and only release the reference. The memory should be freed manually when no longer needed.
/// Use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> constructor and <see cref="MemCpy(void*, void*, nuint)"/> if you are not sure what you are doing.
/// </remarks>
public UnTypedArray(void* buffer, uint size)
{
_buffer = buffer;
_size = size;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ref T GetElementAt<T>(uint index)
where T : unmanaged
{
return ref UnsafeUtilities.ReadArrayElementRef<T>(_buffer, index);
}
/// <inheritdoc/>
public void Resize(uint newSize)
{
if (newSize == _size)
{
return;
}
_buffer = _handle->Realloc(_handle->Allocator, _buffer, newSize, _alignment);
_size = newSize;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Clear()
{
MemClear(_buffer, _size);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _buffer;
}
/// <inheritdoc/>
public void Dispose()
{
if (!IsCreated)
{
return;
}
if (_handle != null)
{
_handle->Free(_handle->Allocator, _buffer);
}
_handle = null;
_buffer = null;
_size = 0;
_alignment = 0;
}
}

View File

@@ -81,7 +81,21 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range."); throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
} }
return ref _buffer[index]; return ref UnsafeUtilities.ReadArrayElementRef<T>(_buffer, index);
}
}
public readonly ref T this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if (index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
}
return ref UnsafeUtilities.ReadArrayElementRef<T>(_buffer, index);
} }
} }
@@ -98,10 +112,17 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator. /// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
/// </summary> /// </summary>
public UnsafeArray() public UnsafeArray()
: this(1, Allocator.Persistent) : this(0, Allocator.Invalid)
{ {
} }
/// <summary>
/// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation handle.
/// </summary>
/// <param name="count">Specifies the number of elements to allocate in the array, which must be greater than zero.</param>
/// <param name="handle">A reference to an AllocationHandle that manages the memory allocation for the array.</param>
/// <param name="allocationOption">Specifies how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
public UnsafeArray(int count, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None) public UnsafeArray(int count, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{ {
if (count <= 0) if (count <= 0)
@@ -136,11 +157,10 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
/// Disposing of the UnsafeArray does not free the memory and only release the reference. The memory should be freed manually when no longer needed. /// Disposing of the UnsafeArray does not free the memory and only release the reference. The memory should be freed manually when no longer needed.
/// Use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> constructor and <see cref="MemCpy(void*, void*, nuint)"/> if you are not sure what you are doing. /// Use <see cref="UnsafeArray(int, Allocator, AllocationOption)"/> constructor and <see cref="MemCpy(void*, void*, nuint)"/> if you are not sure what you are doing.
/// </remarks> /// </remarks>
public UnsafeArray(void* buffer, int count) public UnsafeArray(T* buffer, int count)
{ {
_buffer = (T*)buffer; _buffer = buffer;
_count = count; _count = count;
_handle = (AllocationHandle*)Unsafe.AsPointer(ref AllocationManager.EmptyAllocator.Handle);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -177,7 +197,10 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
return; return;
} }
if (_handle != null)
{
_handle->Free(_handle->Allocator, _buffer); _handle->Free(_handle->Allocator, _buffer);
}
_handle = null; _handle = null;
_buffer = null; _buffer = null;

View File

@@ -215,9 +215,4 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeCollection<KeyValuePai
{ {
_hashMap.Dispose(); _hashMap.Dispose();
} }
public void Test(ref HashMapHelper<TKey> t)
{
Console.WriteLine(t.Equals(_hashMap));
}
} }

View File

@@ -1,4 +1,6 @@
using Misaki.HighPerformance.LowLevel.Collections.Contracts; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers; using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections; using System.Collections;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -81,7 +83,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
/// </summary> /// </summary>
public UnsafeList<T>* listData; public UnsafeList<T>* listData;
internal unsafe ParallelWriter(UnsafeList<T>* list) internal ParallelWriter(UnsafeList<T>* list)
{ {
listData = list; listData = list;
} }
@@ -102,11 +104,15 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
/// </summary> /// </summary>
/// <param name="ptr">Points to the source data to be copied into the buffer.</param> /// <param name="ptr">Points to the source data to be copied into the buffer.</param>
/// <param name="count">Indicates the number of elements to be added from the source data.</param> /// <param name="count">Indicates the number of elements to be added from the source data.</param>
public void AddRangeNoResize(T* ptr, int count) public void AddRangeNoResize(ReadOnlySpan<T> collection, int count)
{ {
var idx = Interlocked.Add(ref listData->_count, count) - count; var index = Interlocked.Add(ref listData->_count, count) - count;
listData->CheckNoResizeCapacity(idx, count); listData->CheckNoResizeCapacity(index, count);
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(listData->_array.GetUnsafePtr(), idx), ptr, (uint)(count * sizeof(T)));
fixed (T* pCollection = collection)
{
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(listData->_array.GetUnsafePtr(), index), pCollection, (uint)(count * sizeof(T)));
}
} }
} }
@@ -124,18 +130,47 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
get => ref _array[index]; get => ref _array[index];
} }
public readonly ref T this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _array[index];
}
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeList<T>*)UnsafeUtilities.AddressOf(ref this)); public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeList<T>*)UnsafeUtilities.AddressOf(ref this));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Provides a parallel writer for the current list, enabling thread-safe additions to the list.
/// </summary>
/// <returns>A <see cref="ParallelWriter"/> instance that can be used to add items to the list in a thread-safe manner.</returns>
public ParallelWriter AsParallelWriter() => new((UnsafeList<T>*)UnsafeUtilities.AddressOf(ref this)); public ParallelWriter AsParallelWriter() => new((UnsafeList<T>*)UnsafeUtilities.AddressOf(ref this));
public UnsafeList() : this(1, Allocator.Persistent) /// <summary>
/// Converts the current list to an UnsafeArray representation.
/// </summary>
/// <returns>A new <see cref="UnsafeArray{T}"/> instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray() => new((T*)_array.GetUnsafePtr(), _count);
public UnsafeList()
: this(0, Allocator.Invalid)
{ {
} }
public UnsafeList(int capacity, ref AllocationHandle handle, AllocationOption allocationType = AllocationOption.None)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
}
_array = new UnsafeArray<T>(capacity, ref handle, allocationType);
_count = 0;
}
public UnsafeList(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.None) public UnsafeList(int capacity, Allocator allocator, AllocationOption allocationType = AllocationOption.None)
{ {
_array = new UnsafeArray<T>(capacity, allocator, allocationType); _array = new UnsafeArray<T>(capacity, allocator, allocationType);
_count = 0;
} }
private readonly void CheckNoResizeCapacity(int count) private readonly void CheckNoResizeCapacity(int count)
@@ -176,6 +211,10 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
} }
} }
/// <summary>
/// Adds a new element to the end of the list, resizing the internal array if necessary.
/// </summary>
/// <param name="value">The element to be added to the list.</param>
public void Add(T value) public void Add(T value)
{ {
if (_count >= Capacity) if (_count >= Capacity)
@@ -187,6 +226,10 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count++; _count++;
} }
/// <summary>
/// Adds the specified value to the collection without resizing the underlying storage.
/// </summary>
/// <param name="value">The value to add to the collection.</param>
public void AddNoResize(T value) public void AddNoResize(T value)
{ {
CheckNoResizeCapacity(1); CheckNoResizeCapacity(1);
@@ -195,6 +238,12 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count++; _count++;
} }
/// <summary>
/// Adds a range of elements to the collection.
/// </summary>
/// <param name="values">A span containing the elements to add. The span must not exceed the specified <paramref name="count"/>.</param>
/// <param name="count">The number of elements to add from the <paramref name="values"/> span. Must be non-negative and less than or
/// equal to the length of <paramref name="values"/>.</param>
public void AddRange(Span<T> values, int count) public void AddRange(Span<T> values, int count)
{ {
var newSize = _count + count; var newSize = _count + count;
@@ -211,18 +260,27 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count += count; _count += count;
} }
public void AddRangeNoResize(ReadOnlySpan<T> values) /// <summary>
/// Adds the elements of the specified collection to the current list without resizing the underlying storage.
/// </summary>
/// <param name="collection">A read-only span containing the elements to add. The span must not exceed the available capacity.</param>
public void AddRangeNoResize(ReadOnlySpan<T> collection)
{ {
CheckNoResizeCapacity(values.Length); CheckNoResizeCapacity(collection.Length);
fixed (T* ptr = values) fixed (T* pCollection = collection)
{ {
MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), ptr, (uint)(values.Length * sizeof(T))); MemCpy(UnsafeUtilities.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), _count), pCollection, (uint)(collection.Length * sizeof(T)));
} }
_count += values.Length; _count += collection.Length;
} }
/// <summary>
/// Adds a range of elements from a pointer to the collection without resizing the underlying storage.
/// </summary>
/// <param name="ptr">Points to the source data to be copied into the collection.</param>
/// <param name="count">Indicates the number of elements to be added from the source data.</param>
public void AddRangeNoResize(T* ptr, int count) public void AddRangeNoResize(T* ptr, int count)
{ {
CheckNoResizeCapacity(count); CheckNoResizeCapacity(count);
@@ -231,6 +289,11 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count += count; _count += count;
} }
/// <summary>
/// Removes a range of elements from the list starting at the specified index.
/// </summary>
/// <param name="start">The zero-based index at which to start removing elements.</param>
/// <param name="length">The number of elements to remove.</param>
public void RemoveRange(int start, int length) public void RemoveRange(int start, int length)
{ {
CheckIndexCount(start, length); CheckIndexCount(start, length);
@@ -248,11 +311,20 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count -= length; _count -= length;
} }
/// <summary>
/// Removes the element at the specified index from the collection.
/// </summary>
/// <param name="index">The zero-based index of the element to remove.</param>
public void RemoveAt(int index) public void RemoveAt(int index)
{ {
RemoveRange(index, 1); RemoveRange(index, 1);
} }
/// <summary>
/// Removes a range of elements from the list starting at the specified index by swapping them with the last elements.
/// </summary>
/// <param name="start">The zero-based index at which to start removing elements.</param>
/// <param name="length">The number of elements to remove.</param>
public void RemoveRangeSwapBack(int start, int length) public void RemoveRangeSwapBack(int start, int length)
{ {
CheckIndexCount(start, length); CheckIndexCount(start, length);
@@ -270,6 +342,11 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count -= length; _count -= length;
} }
/// <summary>
/// Removes the element at the specified index by swapping it with the last element and reducing the collection
/// size.
/// </summary>
/// <param name="index">The zero-based index of the element to remove. Must be within the bounds of the collection.</param>
public void RemoveAtSwapBack(int index) public void RemoveAtSwapBack(int index)
{ {
RemoveRangeSwapBack(index, 1); RemoveRangeSwapBack(index, 1);
@@ -291,27 +368,12 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
_count = 0; _count = 0;
} }
/// <summary>
/// Returns a pointer to the underlying data of the array in an unsafe manner. This method is optimized for
/// performance.
/// </summary>
/// <returns>A pointer to the array's data.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr() public readonly void* GetUnsafePtr()
{ {
return _array.GetUnsafePtr(); return _array.GetUnsafePtr();
} }
/// <summary>
/// Converts the current array to an UnsafeArray representation using its pointer and count.
/// </summary>
/// <returns>Returns a new UnsafeArray instance initialized with the array's unsafe pointer and its count.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray()
{
return new UnsafeArray<T>(_array.GetUnsafePtr(), _count);
}
public void Dispose() public void Dispose()
{ {
_array.Dispose(); _array.Dispose();

View File

@@ -1,3 +1,4 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts; using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers; using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections; using System.Collections;
@@ -90,6 +91,21 @@ public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
_array = new UnsafeArray<T>(capacity, allocator, allocationType); _array = new UnsafeArray<T>(capacity, allocator, allocationType);
} }
/// <summary>
/// Returns a reference to the item at the front of the queue without removing it.
/// </summary>
/// <returns>A reference to the item at the front of the queue.</returns>
/// <exception cref="InvalidOperationException">Thrown if the queue is empty.</exception>
public readonly ref T Peek()
{
if (_count == 0)
{
throw new InvalidOperationException("Queue is empty.");
}
return ref UnsafeUtilities.ReadArrayElementRef<T>(_array.GetUnsafePtr(), _offset);
}
/// <summary> /// <summary>
/// Adds an element to the end of a collection, resizing if the current capacity is reached. The new element is /// Adds an element to the end of a collection, resizing if the current capacity is reached. The new element is
/// stored in a circular buffer. /// stored in a circular buffer.

View File

@@ -0,0 +1,528 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// A sparse set data structure that provides O(1) insertion, deletion, and lookup operations.
/// The sparse set uses three arrays: a dense array for storing values, a sparse array for mapping indices,
/// and a reverse array for mapping dense indices back to sparse indices.
/// Sparse indices work like entity IDs and are automatically generated.
/// </summary>
/// <typeparam name="T">Represents a type that can be stored in the sparse set, constrained to unmanaged types for performance and safety.</typeparam>
public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
where T : unmanaged
{
public struct Enumerator : IEnumerator<T>
{
private UnsafeSparseSet<T>* _collection;
private int _index;
private T _value;
public Enumerator(UnsafeSparseSet<T>* collection)
{
_collection = collection;
_index = -1;
_value = default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
_index++;
if (_index < _collection->_count)
{
_value = UnsafeUtilities.ReadArrayElement<T>(_collection->_dense.GetUnsafePtr(), _index);
return true;
}
_value = default;
return false;
}
public void Reset()
{
_index = -1;
}
public readonly T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _value;
}
readonly object IEnumerator.Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Current;
}
public unsafe readonly void Dispose()
{
}
}
public struct ParallelWriter
{
private UnsafeSparseSet<T>* _sparseSet;
internal ParallelWriter(UnsafeSparseSet<T>* sparseSet)
{
_sparseSet = sparseSet;
}
/// <summary>
/// Adds a value to the sparse set without resizing the internal arrays.
/// </summary>
/// <param name="value">The value to add to the sparse set.</param>
/// <returns> Returns the sparse index assigned to the value. -1 if the sparse index is out of bounds.</returns>
public int AddNoResize(T value)
{
int sparseIndex;
if (_sparseSet->_freeCount > 0)
{
var index = Interlocked.Decrement(ref _sparseSet->_freeCount);
sparseIndex = _sparseSet->_freeList[index];
}
else
{
sparseIndex = Interlocked.Increment(ref _sparseSet->_nextId) - 1;
}
if (sparseIndex >= _sparseSet->_sparse.Count)
{
return -1;
}
var count = Interlocked.Increment(ref _sparseSet->_count) - 1;
_sparseSet->_dense[count] = value;
_sparseSet->_sparse[sparseIndex] = count;
_sparseSet->_reverse[count] = sparseIndex;
return sparseIndex;
}
/// <summary>
/// Attempts to add a value at the specified sparse index without resizing the underlying collection.
/// </summary>
/// <param name="sparseIndex">The index in the sparse array where the value should be added. Must be within the valid range of the sparse
/// array.</param>
/// <param name="value">The value to add to the collection.</param>
/// <returns><see langword="true"/> if the value was successfully added at the specified index; otherwise, <see
/// langword="false"/>.</returns>
public bool AddAtNoResize(int sparseIndex, T value)
{
if (sparseIndex < 0 || sparseIndex >= _sparseSet->_sparse.Count)
{
return false;
}
if (_sparseSet->Contains(sparseIndex))
{
return false;
}
if (_sparseSet->_count >= _sparseSet->_dense.Count)
{
return false;
}
var count = Interlocked.Increment(ref _sparseSet->_count) - 1;
_sparseSet->_dense[count] = value;
_sparseSet->_sparse[sparseIndex] = count;
_sparseSet->_reverse[count] = sparseIndex;
return true;
}
/// <summary>
/// Removes a value at the specified sparse index without resizing the internal arrays.
/// </summary>
/// <param name="sparseIndex">The sparse index of the value to remove.</param>
/// <returns>Returns <see langword="true"/> if the value was successfully removed; otherwise, <see langword="false"/>.</returns>
public bool RemoveNoResize(int sparseIndex)
{
if (!_sparseSet->Contains(sparseIndex))
{
return false;
}
var denseIndex = _sparseSet->_sparse[sparseIndex];
var lastIndex = _sparseSet->_count - 1;
if (denseIndex != lastIndex)
{
var lastValue = _sparseSet->_dense[lastIndex];
var lastSparseIndex = _sparseSet->_reverse[lastIndex];
_sparseSet->_dense[denseIndex] = lastValue;
_sparseSet->_reverse[denseIndex] = lastSparseIndex;
_sparseSet->_sparse[lastSparseIndex] = denseIndex;
}
_sparseSet->_sparse[sparseIndex] = -1;
if (_sparseSet->_freeCount >= _sparseSet->_freeList.Count)
{
return false;
}
_sparseSet->_freeList[_sparseSet->_freeCount] = sparseIndex;
Interlocked.Increment(ref _sparseSet->_freeCount);
Interlocked.Decrement(ref _sparseSet->_count);
return true;
}
}
private UnsafeArray<T> _dense;
private UnsafeArray<int> _sparse;
private UnsafeArray<int> _reverse; // Maps dense index to sparse index
private UnsafeArray<int> _freeList; // Stack of available sparse indices
private int _count;
private int _nextId; // Next available sparse index
private int _freeCount; // Number of items in the free list
public readonly int Count => _count;
public readonly int Capacity => _dense.Count;
public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated && _freeList.IsCreated;
public readonly ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _dense[index];
}
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeSparseSet<T>*)UnsafeUtilities.AddressOf(ref this));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Constructs an UnsafeSparseSet with a default size of 1 and uses the Persistent allocator.
/// </summary>
public UnsafeSparseSet()
: this(1, Allocator.Persistent)
{
}
/// <summary>
/// Initializes a new instance of UnsafeSparseSet with a specified capacity and an allocation handle.
/// </summary>
/// <param name="capacity">Specifies the initial capacity of the sparse set, which must be greater than zero.</param>
/// <param name="handle">A reference to an AllocationHandle that manages the memory allocation for the sparse set.</param>
/// <param name="allocationOption">Specifies how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified capacity is less than or equal to zero.</exception>
public UnsafeSparseSet(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
}
_dense = new UnsafeArray<T>(capacity, ref handle, allocationOption);
_sparse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
_reverse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
_freeList = new UnsafeArray<int>(capacity, ref handle, allocationOption);
_count = 0;
_nextId = 0;
_freeCount = 0;
Clear();
}
/// <summary>
/// Initializes a new instance of UnsafeSparseSet with a specified capacity and an allocation type.
/// </summary>
/// <param name="capacity">Specifies the initial capacity of the sparse set, which must be greater than zero.</param>
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified capacity is less than or equal to zero.</exception>
public UnsafeSparseSet(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
}
/// <summary>
/// Adds a value to the sparse set and returns a unique sparse index for the value.
/// </summary>
/// <param name="value">The value to add to the sparse set.</param>
/// <returns>A unique sparse index that can be used to reference this value.</returns>
public int Add(T value)
{
int sparseIndex;
// Try to reuse a freed sparse index first
if (_freeCount > 0)
{
_freeCount--;
sparseIndex = _freeList[_freeCount];
}
else
{
// Use the next available ID
sparseIndex = _nextId++;
// Resize sparse array if necessary
if (sparseIndex >= _sparse.Count)
{
ResizeSparse(sparseIndex + 1);
}
}
// Resize dense arrays if necessary
if (_count >= _dense.Count)
{
var newCapacity = _dense.Count + Math.Max(1, _dense.Count / 2);
_dense.Resize(newCapacity);
_reverse.Resize(newCapacity);
}
// Add the value to the dense array and update mappings
_dense[_count] = value;
_sparse[sparseIndex] = _count;
_reverse[_count] = sparseIndex;
_count++;
return sparseIndex;
}
/// <summary>
/// Adds a value to the sparse set at the specified sparse index.
/// This method is provided for compatibility when you need to specify the exact sparse index.
/// </summary>
/// <param name="sparseIndex">The index in the sparse array where the value should be mapped.</param>
/// <param name="value">The value to add to the sparse set.</param>
/// <returns>True if the value was added, false if the sparse index is already occupied.</returns>
public bool AddAt(int sparseIndex, T value)
{
if (sparseIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index must be non-negative.");
}
if (sparseIndex >= _sparse.Count)
{
ResizeSparse(sparseIndex + 1);
}
if (Contains(sparseIndex))
{
return false;
}
if (_count >= _dense.Count)
{
var newCapacity = _dense.Count + Math.Max(1, _dense.Count / 2);
_dense.Resize(newCapacity);
_reverse.Resize(newCapacity);
}
// Add the value to the dense array and update mappings
_dense[_count] = value;
_sparse[sparseIndex] = _count;
_reverse[_count] = sparseIndex; // Store reverse mapping
_count++;
// Update _nextId if we're using a higher ID
if (sparseIndex >= _nextId)
{
_nextId = sparseIndex + 1;
}
return true;
}
/// <summary>
/// Removes the value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index of the value to remove.</param>
/// <returns>True if the value was removed, false if the sparse index was not found.</returns>
public bool Remove(int sparseIndex)
{
if (!Contains(sparseIndex))
{
return false;
}
var denseIndex = _sparse[sparseIndex];
var lastIndex = _count - 1;
if (denseIndex != lastIndex)
{
// Move the last element to the position of the removed element
var lastValue = _dense[lastIndex];
var lastSparseIndex = _reverse[lastIndex]; // Get sparse index of last element
_dense[denseIndex] = lastValue;
_reverse[denseIndex] = lastSparseIndex;
// Update the sparse mapping for the moved element
_sparse[lastSparseIndex] = denseIndex;
}
// Mark the sparse index as unused and add to free list
_sparse[sparseIndex] = -1;
// Add the freed sparse index to the free list for reuse
if (_freeCount >= _freeList.Count)
{
_freeList.Resize(_freeList.Count + Math.Max(1, _freeList.Count / 2));
}
_freeList[_freeCount] = sparseIndex;
_freeCount++;
_count--;
return true;
}
/// <summary>
/// Checks if the sparse set contains a value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index to check.</param>
/// <returns>True if the sparse index is valid and contains a value, false otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Contains(int sparseIndex)
{
if (sparseIndex < 0 || sparseIndex >= _sparse.Count)
{
return false;
}
var denseIndex = _sparse[sparseIndex];
return denseIndex >= 0 && denseIndex < _count;
}
/// <summary>
/// Gets the value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
/// <param name="value">When this method returns, contains the value at the specified sparse index, if found.</param>
/// <returns>True if the sparse index contains a value, false otherwise.</returns>
public readonly bool TryGetValue(int sparseIndex, out T value)
{
if (Contains(sparseIndex))
{
var denseIndex = _sparse[sparseIndex];
value = _dense[denseIndex];
return true;
}
value = default;
return false;
}
/// <summary>
/// Gets the value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
/// <returns>The value at the specified sparse index.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception>
public readonly T GetValue(int sparseIndex)
{
if (!Contains(sparseIndex))
{
throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index not found in the set.");
}
var denseIndex = _sparse[sparseIndex];
return _dense[denseIndex];
}
/// <summary>
/// Updates the value at the specified sparse index.
/// </summary>
/// <param name="sparseIndex">The sparse index of the value to update.</param>
/// <param name="value">The new value.</param>
/// <returns>True if the value was updated, false if the sparse index was not found.</returns>
public bool SetValue(int sparseIndex, T value)
{
if (!Contains(sparseIndex))
{
return false;
}
var denseIndex = _sparse[sparseIndex];
_dense[denseIndex] = value;
return true;
}
private void ResizeSparse(int newSize)
{
var oldSize = _sparse.Count;
_sparse.Resize(newSize);
_sparse.AsSpan()[oldSize..newSize].Fill(-1);
}
/// <inheritdoc/>
public void Clear()
{
if (!IsCreated)
{
return;
}
_sparse.AsSpan().Fill(-1);
_count = 0;
_nextId = 0;
_freeCount = 0;
}
/// <inheritdoc/>
public void Resize(int newSize)
{
if (newSize <= 0)
{
throw new ArgumentOutOfRangeException(nameof(newSize), "New size must be greater than zero.");
}
_dense.Resize(newSize);
_reverse.Resize(newSize);
_freeList.Resize(newSize);
if (newSize > _sparse.Count)
{
ResizeSparse(newSize);
}
if (_count > newSize)
{
_count = newSize;
}
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void* GetUnsafePtr()
{
return _dense.GetUnsafePtr();
}
/// <summary>
/// Converts the current sparse set to an UnsafeArray representation using its dense array.
/// </summary>
/// <returns>Returns a new UnsafeArray instance initialized with the dense array's pointer and count.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly UnsafeArray<T> AsUnsafeArray()
{
return new UnsafeArray<T>((T*)_dense.GetUnsafePtr(), _count);
}
/// <inheritdoc/>
public void Dispose()
{
_dense.Dispose();
_sparse.Dispose();
_reverse.Dispose();
_freeList.Dispose();
_count = 0;
_nextId = 0;
_freeCount = 0;
}
}

View File

@@ -1,4 +1,5 @@
using Misaki.HighPerformance.LowLevel.Collections.Contracts; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers; using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections; using System.Collections;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;

View File

@@ -1,33 +1,50 @@
using Misaki.HighPerformance.LowLevel.Collections; namespace Misaki.HighPerformance.LowLevel.Contracts;
namespace Misaki.HighPerformance.LowLevel.Contracts;
using unsafe AllocFunc = delegate* unmanaged<void*, nuint, nuint, AllocationOption, void*>;
using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>;
using unsafe ReallocFunc = delegate* unmanaged<void*, void*, nuint, nuint, void*>;
/// <summary>
/// A structure that encapsulates function pointers for memory allocation operations.
/// </summary>
public unsafe readonly struct AllocationHandle public unsafe readonly struct AllocationHandle
{ {
/// <summary>
/// Gets a pointer to the allocator instance associated with this allocation handle.
/// </summary>
public void* Allocator public void* Allocator
{ {
get; get;
} }
/// <summary>
/// Gets a function pointer for allocating memory.
/// </summary>
public AllocFunc Alloc public AllocFunc Alloc
{ {
get; get;
} }
/// <summary>
/// Gets a function pointer for reallocating memory.
/// </summary>
public ReallocFunc Realloc public ReallocFunc Realloc
{ {
get; get;
} }
/// <summary>
/// Gets a function pointer for freeing allocated memory.
/// </summary>
public FreeFunc Free public FreeFunc Free
{ {
get; get;
} }
/// <summary>
/// Initializes a new instance of the <see cref="AllocationHandle"/> struct with the specified allocator and memory
/// management functions.
/// </summary>
/// <param name="allocator">A pointer to the allocator instance used for memory management.</param>
/// <param name="alloc">The function used to allocate memory.</param>
/// <param name="realloc">The function used to reallocate memory.</param>
/// <param name="free">The function used to free allocated memory.</param>
public AllocationHandle(void* allocator, AllocFunc alloc, ReallocFunc realloc, FreeFunc free) public AllocationHandle(void* allocator, AllocFunc alloc, ReallocFunc realloc, FreeFunc free)
{ {
Allocator = allocator; Allocator = allocator;
@@ -42,9 +59,13 @@ public unsafe readonly struct AllocationHandle
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The allocator must be static or pined to a specific memory region. /// The allocator must be static or pined to a specific memory region.
/// Otherwise the pointer of the allocator, <see cref="AllocationHandle.Allocator"/>, may become invalid and lead to undefined behavior.
/// </remarks> /// </remarks>
public unsafe interface IAllocator public unsafe interface IAllocator
{ {
/// <summary>
/// Gets a reference to the allocation handle associated with this allocator.
/// </summary>
public ref AllocationHandle Handle public ref AllocationHandle Handle
{ {
get; get;

View File

@@ -4,8 +4,11 @@ using System.Text;
namespace Misaki.HighPerformance.LowLevel.Exceptions; namespace Misaki.HighPerformance.LowLevel.Exceptions;
/// <summary>
public class MemoryLeakException(params AllocationManager.AllocationInfo[] Infos) : Exception /// An exception that is thrown when a memory leak is detected.
/// </summary>
/// <param name="Infos">An array of AllocationInfo containing details about the memory leaks.</param>
public class MemoryLeakException(params AllocationInfo[] Infos) : Exception
{ {
private static string GetMessage(StackTrace? stackTrace) private static string GetMessage(StackTrace? stackTrace)
{ {

View File

@@ -2,11 +2,18 @@
namespace Misaki.HighPerformance.LowLevel; namespace Misaki.HighPerformance.LowLevel;
/// <summary>
/// A structure that encapsulates a function pointer and provides methods to convert between
/// </summary>
/// <remarks>
/// This structure used marshalling to convert between function pointers and delegates, which is not ideal for high-performance scenarios.
/// Use this only when necessary, and prefer using <c>delegate* unmanaged</c> for better performance.
/// </remarks>
/// <typeparam name="T">The delegate type that the function pointer represents.</typeparam>
public readonly struct FunctionPointer<T> public readonly struct FunctionPointer<T>
where T : Delegate where T : Delegate
{ {
private readonly nint _ptr; private readonly nint _ptr;
private readonly T _delegate;
/// <summary> /// <summary>
/// Gets the native function pointer associated with this function pointer instance. /// Gets the native function pointer associated with this function pointer instance.
@@ -20,15 +27,14 @@ public readonly struct FunctionPointer<T>
/// cref="Marshal.GetDelegateForFunctionPointer{TDelegate}"/> to convert the function /// cref="Marshal.GetDelegateForFunctionPointer{TDelegate}"/> to convert the function
/// pointer to a delegate. Ensure that the function pointer is valid and compatible with the delegate type /// pointer to a delegate. Ensure that the function pointer is valid and compatible with the delegate type
/// <typeparamref name="T"/>.</remarks> /// <typeparamref name="T"/>.</remarks>
public T Invoke => _delegate; public T Delegate => Marshal.GetDelegateForFunctionPointer<T>(_ptr);
/// <summary> /// <summary>
/// Creates a new instance of this function pointer with the following native pointer. /// Creates a new instance of this function pointer with the following native pointer.
/// </summary> /// </summary>
/// <param name="ptr"></param> /// <param name="ptr"></param>
public FunctionPointer(nint ptr) public FunctionPointer(T func)
{ {
_ptr = ptr; _ptr = Marshal.GetFunctionPointerForDelegate<T>(func);
_delegate = Marshal.GetDelegateForFunctionPointer<T>(ptr);
} }
} }

View File

@@ -1,3 +1,4 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Contracts; using Misaki.HighPerformance.LowLevel.Contracts;
using System.Numerics; using System.Numerics;
@@ -79,35 +80,15 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
public const int MINIMAL_CAPACITY = 64; public const int MINIMAL_CAPACITY = 64;
public readonly byte* Buffer public readonly byte* Buffer => _buffer;
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _buffer;
}
public readonly int Count public readonly int Count => _count;
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _count;
}
public readonly int Capacity public readonly int Capacity => _capacity;
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _capacity;
}
public readonly bool IsCreated public readonly bool IsCreated => _buffer != null;
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _buffer != null;
}
public readonly bool IsEmpty public readonly bool IsEmpty => !IsCreated || _count == 0;
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => !IsCreated || _count == 0;
}
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset) private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
{ {

View File

@@ -168,7 +168,7 @@ public static unsafe partial class MemoryUtilities
} }
/// <summary> /// <summary>
/// Calculates the alignment size difference between a specified struct and a helper struct. /// Calculates the alignment size of a specified struct.
/// </summary> /// </summary>
/// <typeparam name="T">Represents a value type that is used to determine the alignment size.</typeparam> /// <typeparam name="T">Represents a value type that is used to determine the alignment size.</typeparam>
/// <returns>Returns the size difference in bytes as an integer.</returns> /// <returns>Returns the size difference in bytes as an integer.</returns>

View File

@@ -1,4 +1,5 @@
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Collections.Contracts; using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -25,9 +26,9 @@ public unsafe static class UnsafeCollectionExtensions
throw new ArgumentException("Source collection is larger than the destination span."); throw new ArgumentException("Source collection is larger than the destination span.");
} }
fixed (T* ptr = destination) fixed (T* pDest = destination)
{ {
SystemUnsfae.CopyBlock(ptr, source.GetUnsafePtr(), (uint)(source.Count * sizeof(T))); MemCpy(source.GetUnsafePtr(), pDest, (uint)(source.Count * sizeof(T)));
} }
} }
@@ -41,7 +42,7 @@ public unsafe static class UnsafeCollectionExtensions
/// <param name="destinationIndex">The starting index in the destination span where the elements will be placed.</param> /// <param name="destinationIndex">The starting index in the destination span where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source to the destination.</param> /// <param name="length">The number of elements to copy from the source to the destination.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception> /// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
public static void CopyTo<T>(this IUnsafeCollection<T> source, Span<T> destination, int sourceIndex, int destinationIndex, int length) public static void CopyTo<T>(this IUnsafeCollection<T> source, Span<T> destination, uint sourceIndex, uint destinationIndex, uint length)
where T : unmanaged where T : unmanaged
{ {
if (sourceIndex + length > source.Count || destinationIndex + length > destination.Length) if (sourceIndex + length > source.Count || destinationIndex + length > destination.Length)
@@ -49,9 +50,56 @@ public unsafe static class UnsafeCollectionExtensions
throw new ArgumentException("Source collection or destination span is too small for the specified range."); throw new ArgumentException("Source collection or destination span is too small for the specified range.");
} }
fixed (T* ptr = destination) fixed (T* pDest = destination)
{ {
SystemUnsfae.CopyBlock(ptr + destinationIndex, (byte*)source.GetUnsafePtr() + sourceIndex * sizeof(T), (uint)(length * sizeof(T))); MemCpy((byte*)source.GetUnsafePtr() + sourceIndex * sizeof(T), pDest + destinationIndex, (uint)(length * sizeof(T)));
}
}
/// <summary>
/// Copies elements from an untyped source collection to a destination span of a specific type.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the destination span, which must be unmanaged.</typeparam>
/// <param name="source">The untyped collection from which data is copied.</param>
/// <param name="destination">The typed span where the data will be copied to.</param>
/// <exception cref="ArgumentException">Thrown when the source collection size exceeds the destination span capacity.</exception>
public static void CopyTo<T>(this IUnTypedCollection source, Span<T> destination)
where T : unmanaged
{
var destSize = (uint)destination.Length * (uint)sizeof(T);
if (source.Size > destSize)
{
throw new ArgumentException("Source collection is larger than the destination span.");
}
fixed (T* pDest = destination)
{
MemCpy(source.GetUnsafePtr(), pDest, source.Size);
}
}
/// <summary>
/// Copies a range of bytes from an untyped source collection to a destination span, interpreting the bytes as elements of type T.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the destination span, which must be unmanaged.</typeparam>
/// <param name="source">The untyped collection from which bytes are copied.</param>
/// <param name="destination">The typed span where the elements will be placed.</param>
/// <param name="sourceOffset">The byte offset in the source collection from which to start copying.</param>
/// <param name="destinationIndex">The element index in the destination span where copying will begin.</param>
/// <param name="length">The number of elements of type T to copy.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source collection or destination span.</exception>
public static void CopyTo<T>(this IUnTypedCollection source, Span<T> destination, uint sourceOffset, uint destinationIndex, uint length)
where T : unmanaged
{
var sizeOfElement = (uint)sizeof(T);
if (sourceOffset + (length * sizeOfElement) > source.Size || destinationIndex + length > destination.Length)
{
throw new ArgumentException("Source collection or destination span is too small for the specified range.");
}
fixed (T* pDest = destination)
{
MemCpy((byte*)source.GetUnsafePtr() + sourceOffset, pDest + destinationIndex, length * sizeOfElement);
} }
} }
@@ -62,17 +110,17 @@ public unsafe static class UnsafeCollectionExtensions
/// <param name="destination">Represents the unsafe collection that will receive the copied elements.</param> /// <param name="destination">Represents the unsafe collection that will receive the copied elements.</param>
/// <param name="source">Represents the span containing the elements to be copied to the unsafe collection.</param> /// <param name="source">Represents the span containing the elements to be copied to the unsafe collection.</param>
/// <exception cref="ArgumentException">Thrown when the source span and destination collection have different sizes.</exception> /// <exception cref="ArgumentException">Thrown when the source span and destination collection have different sizes.</exception>
public static void CopyFrom<T>(this IUnsafeCollection<T> destination, Span<T> source) public static void CopyFrom<T>(this IUnsafeCollection<T> destination, ReadOnlySpan<T> source)
where T : unmanaged where T : unmanaged
{ {
if (destination.Count > source.Length) if (destination.Count < source.Length)
{ {
throw new ArgumentException("Destination collection is larger than the source span."); throw new ArgumentException("Destination collection is smaller than the source span.");
} }
fixed (T* ptr = source) fixed (T* pSrc = source)
{ {
SystemUnsfae.CopyBlock(destination.GetUnsafePtr(), ptr, (uint)(source.Length * sizeof(T))); MemCpy(pSrc, destination.GetUnsafePtr(), (uint)(source.Length * sizeof(T)));
} }
} }
@@ -86,7 +134,7 @@ public unsafe static class UnsafeCollectionExtensions
/// <param name="destinationIndex">The starting index in the destination collection where the elements will be placed.</param> /// <param name="destinationIndex">The starting index in the destination collection where the elements will be placed.</param>
/// <param name="length">The number of elements to copy from the source span to the destination collection.</param> /// <param name="length">The number of elements to copy from the source span to the destination collection.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception> /// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
public static void CopyFrom<T>(this IUnsafeCollection<T> destination, Span<T> source, int sourceIndex, int destinationIndex, int length) public static void CopyFrom<T>(this IUnsafeCollection<T> destination, ReadOnlySpan<T> source, uint sourceIndex, uint destinationIndex, uint length)
where T : unmanaged where T : unmanaged
{ {
if (sourceIndex + length > source.Length || destinationIndex + length > destination.Count) if (sourceIndex + length > source.Length || destinationIndex + length > destination.Count)
@@ -94,9 +142,57 @@ public unsafe static class UnsafeCollectionExtensions
throw new ArgumentException("Source span or destination collection is too small for the specified range."); throw new ArgumentException("Source span or destination collection is too small for the specified range.");
} }
fixed (T* ptr = source) fixed (T* pSrc = source)
{ {
SystemUnsfae.CopyBlock((byte*)destination.GetUnsafePtr() + destinationIndex * sizeof(T), ptr + sourceIndex, (uint)(length * sizeof(T))); MemCpy(pSrc + sourceIndex, (byte*)destination.GetUnsafePtr() + destinationIndex * sizeof(T), (uint)(length * sizeof(T)));
}
}
/// <summary>
/// Copies elements from a typed source span to an untyped destination collection.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the source span, which must be unmanaged.</typeparam>
/// <param name="destination">The untyped collection that will receive the copied data.</param>
/// <param name="source">The typed span containing the elements to be copied.</param>
/// <exception cref="ArgumentException">Thrown when the destination collection is smaller than the source span data size.</exception>
public static void CopyFrom<T>(this IUnTypedCollection destination, ReadOnlySpan<T> source)
where T : unmanaged
{
var sourceSize = (uint)(source.Length * sizeof(T));
if (destination.Size < sourceSize)
{
throw new ArgumentException("Destination collection is smaller than the source span.");
}
fixed (T* pSrc = source)
{
MemCpy(pSrc, destination.GetUnsafePtr(), sourceSize);
}
}
/// <summary>
/// Copies a range of elements from a typed source span to an untyped destination collection at a specified byte offset.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the source span, which must be unmanaged.</typeparam>
/// <param name="destination">The untyped collection where the data will be copied to.</param>
/// <param name="source">The typed span containing the elements to be copied.</param>
/// <param name="sourceIndex">The starting element index in the source span from which to begin copying.</param>
/// <param name="destinationOffset">The byte offset in the destination collection where the data will be placed.</param>
/// <param name="length">The number of elements to copy from the source span.</param>
/// <exception cref="ArgumentException">Thrown when the specified range exceeds the bounds of the source span or destination collection.</exception>
public static void CopyFrom<T>(this IUnTypedCollection destination, ReadOnlySpan<T> source, uint sourceIndex, uint destinationOffset, uint length)
where T : unmanaged
{
var sizeOfElement = (uint)sizeof(T);
if (sourceIndex + length > source.Length || destinationOffset + (length * sizeOfElement) > destination.Size)
{
throw new ArgumentException("Source span or destination collection is too small for the specified range.");
}
fixed (T* pSrc = source)
{
MemCpy(pSrc + sourceIndex, (byte*)destination.GetUnsafePtr() + destinationOffset, length * sizeOfElement);
} }
} }
@@ -112,25 +208,49 @@ public unsafe static class UnsafeCollectionExtensions
return new(source.GetUnsafePtr(), source.Count); return new(source.GetUnsafePtr(), source.Count);
} }
/// <summary>
/// Converts an UnTypedCollection into a Span for efficient memory access.
/// </summary>
/// <param name="source">The UnTypedCollection instance to be converted into a Span.</param>
/// <returns>A Span that provides a view over the elements of the UnsafeCollection.</returns>
public static Span<byte> AsSpan(this IUnTypedCollection source)
{
return new(source.GetUnsafePtr(), (int)source.Size);
}
/// <summary>
/// Converts a managed array to an UnsafeArray by copying its elements to unmanaged memory.
/// </summary>
/// <typeparam name="T">The type of elements in the array, which must be unmanaged.</typeparam>
/// <param name="source">The managed array to convert.</param>
/// <param name="allocator">The allocator to use for memory allocation of the UnsafeArray.</param>
/// <returns>A new UnsafeArray containing a copy of the source array elements.</returns>
public static UnsafeArray<T> ToUnsafeArray<T>(this T[] source, Allocator allocator) public static UnsafeArray<T> ToUnsafeArray<T>(this T[] source, Allocator allocator)
where T : unmanaged where T : unmanaged
{ {
var array = new UnsafeArray<T>(source.Length, allocator); var array = new UnsafeArray<T>(source.Length, allocator);
fixed (T* ptr = source) fixed (T* pSrc = source)
{ {
MemCpy(array.GetUnsafePtr(), ptr, (uint)(source.Length * sizeof(T))); MemCpy(pSrc, array.GetUnsafePtr(), (uint)(source.Length * sizeof(T)));
} }
return array; return array;
} }
/// <summary>
/// Converts a managed List to an UnsafeList by copying its elements to unmanaged memory.
/// </summary>
/// <typeparam name="T">The type of elements in the list, which must be unmanaged.</typeparam>
/// <param name="source">The managed List to convert.</param>
/// <param name="allocator">The allocator to use for memory allocation of the UnsafeList.</param>
/// <returns>A new UnsafeList containing a copy of the source list elements.</returns>
public static UnsafeList<T> ToUnsafeList<T>(this List<T> source, Allocator allocator) public static UnsafeList<T> ToUnsafeList<T>(this List<T> source, Allocator allocator)
where T : unmanaged where T : unmanaged
{ {
var list = new UnsafeList<T>(source.Count, allocator); var list = new UnsafeList<T>(source.Count, allocator);
fixed (T* ptr = CollectionsMarshal.AsSpan(source)) fixed (T* pSrc = CollectionsMarshal.AsSpan(source))
{ {
MemCpy(list.GetUnsafePtr(), ptr, (uint)(source.Count * sizeof(T))); MemCpy(pSrc, list.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
} }
return list; return list;

View File

@@ -13,8 +13,9 @@ public static unsafe class UnsafeUtilities
/// <returns>Returns a reference of the specified type pointing to the given memory address.</returns> /// <returns>Returns a reference of the specified type pointing to the given memory address.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T AsRef<T>(void* ptr) public static ref T AsRef<T>(void* ptr)
where T : unmanaged
{ {
return ref SystemUnsfae.AsRef<T>(ptr); return ref *(T*)ptr;
} }
/// <summary> /// <summary>
@@ -25,8 +26,9 @@ public static unsafe class UnsafeUtilities
/// <returns>A pointer to the memory address of the specified variable.</returns> /// <returns>A pointer to the memory address of the specified variable.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AddressOf<T>(ref T value) public static void* AddressOf<T>(ref T value)
where T : unmanaged
{ {
return SystemUnsfae.AsPointer(ref value); return Unsafe.AsPointer(ref value);
} }
/// <summary> /// <summary>
@@ -37,7 +39,22 @@ public static unsafe class UnsafeUtilities
/// <param name="index">Indicates the position of the element to be accessed within the array.</param> /// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>Returns a pointer to the element located at the specified index.</returns> /// <returns>Returns a pointer to the element located at the specified index.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T* ReadArrayElementUnsafe<T>(void* ptr, int index) where T : unmanaged public static T* ReadArrayElementUnsafe<T>(void* ptr, int index)
where T : unmanaged
{
return (T*)((byte*)ptr + index * sizeof(T));
}
/// <summary>
/// Reads an element from an unmanaged array at a specified index using a pointer.
/// </summary>
/// <typeparam name="T">Specifies the type of elements in the unmanaged array.</typeparam>
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>Returns a pointer to the element located at the specified index.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T* ReadArrayElementUnsafe<T>(void* ptr, uint index)
where T : unmanaged
{ {
return (T*)((byte*)ptr + index * sizeof(T)); return (T*)((byte*)ptr + index * sizeof(T));
} }
@@ -50,7 +67,22 @@ public static unsafe class UnsafeUtilities
/// <param name="index">Indicates the position of the element to be accessed in the array.</param> /// <param name="index">Indicates the position of the element to be accessed in the array.</param>
/// <returns>A reference to the specified element in the unmanaged array.</returns> /// <returns>A reference to the specified element in the unmanaged array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T ReadArrayElementRef<T>(void* ptr, int index) where T : unmanaged public static ref T ReadArrayElementRef<T>(void* ptr, int index)
where T : unmanaged
{
return ref AsRef<T>(ReadArrayElementUnsafe<T>(ptr, index));
}
/// <summary>
/// Reads an element from an unmanaged array using a pointer and index, returning a reference to the element.
/// </summary>
/// <typeparam name="T">Specifies the type of the elements in the unmanaged array.</typeparam>
/// <param name="ptr">Points to the start of the unmanaged array from which the element is read.</param>
/// <param name="index">Indicates the position of the element to be accessed in the array.</param>
/// <returns>A reference to the specified element in the unmanaged array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T ReadArrayElementRef<T>(void* ptr, uint index)
where T : unmanaged
{ {
return ref AsRef<T>(ReadArrayElementUnsafe<T>(ptr, index)); return ref AsRef<T>(ReadArrayElementUnsafe<T>(ptr, index));
} }
@@ -63,7 +95,22 @@ public static unsafe class UnsafeUtilities
/// <param name="index">Indicates the position of the element to be accessed within the array.</param> /// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>The element located at the specified index in the array.</returns> /// <returns>The element located at the specified index in the array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ReadArrayElement<T>(void* ptr, int index) where T : unmanaged public static T ReadArrayElement<T>(void* ptr, int index)
where T : unmanaged
{
return *ReadArrayElementUnsafe<T>(ptr, index);
}
/// <summary>
/// Reads an element from an array at a specified index using a pointer to the array.
/// </summary>
/// <typeparam name="T">Specifies the type of the elements in the array, which must be unmanaged.</typeparam>
/// <param name="ptr">Points to the start of the array from which an element will be read.</param>
/// <param name="index">Indicates the position of the element to be accessed within the array.</param>
/// <returns>The element located at the specified index in the array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ReadArrayElement<T>(void* ptr, uint index)
where T : unmanaged
{ {
return *ReadArrayElementUnsafe<T>(ptr, index); return *ReadArrayElementUnsafe<T>(ptr, index);
} }
@@ -76,7 +123,22 @@ public static unsafe class UnsafeUtilities
/// <param name="index">Indicates the position in the array where the value should be stored.</param> /// <param name="index">Indicates the position in the array where the value should be stored.</param>
/// <param name="value">Represents the value to be written to the specified index of the array.</param> /// <param name="value">Represents the value to be written to the specified index of the array.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteArrayElement<T>(void* ptr, int index, T value) where T : unmanaged public static void WriteArrayElement<T>(void* ptr, int index, T value)
where T : unmanaged
{
*ReadArrayElementUnsafe<T>(ptr, index) = value;
}
/// <summary>
/// Writes a value to a specified index of an unmanaged array using a pointer.
/// </summary>
/// <typeparam name="T">Specifies the type of the value being written to the array, which must be an unmanaged type.</typeparam>
/// <param name="ptr">Points to the beginning of the unmanaged array where the value will be written.</param>
/// <param name="index">Indicates the position in the array where the value should be stored.</param>
/// <param name="value">Represents the value to be written to the specified index of the array.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteArrayElement<T>(void* ptr, uint index, T value)
where T : unmanaged
{ {
*ReadArrayElementUnsafe<T>(ptr, index) = value; *ReadArrayElementUnsafe<T>(ptr, index) = value;
} }
@@ -92,6 +154,6 @@ public static unsafe class UnsafeUtilities
public static UnsafeArray<TOut> CastArray<TIn, TOut>(UnsafeArray<TIn> array) public static UnsafeArray<TOut> CastArray<TIn, TOut>(UnsafeArray<TIn> array)
where TIn : unmanaged where TOut : unmanaged where TIn : unmanaged where TOut : unmanaged
{ {
return new UnsafeArray<TOut>(array.GetUnsafePtr(), array.Count * sizeof(TIn) / sizeof(TOut)); return new UnsafeArray<TOut>((TOut*)array.GetUnsafePtr(), array.Count * sizeof(TIn) / sizeof(TOut));
} }
} }

View File

@@ -0,0 +1,71 @@
using Microsoft.CodeAnalysis;
using Misaki.HighPerformance.Mathematics.CodeGen.Models;
using System.Text;
namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
{
internal abstract class GeneratorBase
{
protected const string INLINE_METHOD_ATTRIBUTE = "[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]";
protected readonly StringBuilder sourceBuilder = new();
protected NumericTypeInfo typeInfo = null!;
public string Generate(NumericTypeInfo typeInfo)
{
this.typeInfo = typeInfo;
sourceBuilder.Clear();
Initialize();
GenerateHeader();
GenerateNamespaceStart();
GenerateTypeStart();
GenerateBody();
GenerateTypeEnd();
GenerateNamespaceEnd();
return sourceBuilder.ToString();
}
protected virtual void Initialize()
{
}
protected virtual void GenerateHeader()
{
sourceBuilder.AppendLine("// <auto-generated/>");
}
protected virtual void GenerateNamespaceStart()
{
sourceBuilder.Append($@"
namespace {typeInfo.TypeSymbol.ContainingNamespace.ToDisplayString()}
{{");
}
protected virtual void GenerateTypeStart()
{
sourceBuilder.Append($@"
[global::System.Runtime.CompilerServices.SkipLocalsInit]
public partial struct {typeInfo.TypeSymbol.Name} : global::System.IEquatable<{typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>
{{");
}
protected abstract void GenerateBody();
protected virtual void GenerateTypeEnd()
{
sourceBuilder.Append($@"
}}");
}
protected virtual void GenerateNamespaceEnd()
{
sourceBuilder.Append($@"
}}");
}
}
}

View File

@@ -0,0 +1,752 @@
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
{
internal class MatrixGenerator : GeneratorBase
{
private readonly string[] _matrixComponents = new[] { "c0", "c1", "c2", "c3" };
private readonly string[] _vectorComponents = new[] { "x", "y", "z", "w" };
protected override void GenerateBody()
{
GenerateField();
if (typeInfo.Arithmetic)
{
GenerateUnitMatrix();
}
GenerateConstructors();
GenerateUnsafeMethod();
GenerateOverrideMethod();
if (typeInfo.Arithmetic)
{
GenerateArithmeticOperators();
}
}
protected override void GenerateTypeEnd()
{
base.GenerateTypeEnd();
sourceBuilder.AppendLine();
GenerateMathMethod();
}
private void GenerateField()
{
for (var i = 0; i < typeInfo.Column; i++)
{
sourceBuilder.Append($@"
public {typeInfo.ComponentTypeFullName} {_matrixComponents[i]};");
}
sourceBuilder.AppendLine();
sourceBuilder.AppendLine($@"
public unsafe ref {typeInfo.ComponentTypeFullName} this[int index] {{
{INLINE_METHOD_ATTRIBUTE}
get => ref (({typeInfo.ComponentTypeFullName}*)global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref this))[index];
}}");
}
private void GenerateUnitMatrix()
{
if (typeInfo.Column == typeInfo.Row)
{
var tempSB = new StringBuilder();
for (var i = 0; i < typeInfo.Column; i++)
{
tempSB.Append($"new {typeInfo.ComponentTypeFullName}(");
for (var j = 0; j < typeInfo.Row; j++)
{
if (i == j)
{
tempSB.Append("1");
}
else
{
tempSB.Append("0");
}
if (j < typeInfo.Row - 1)
{
tempSB.Append(", ");
}
}
tempSB.Append(")");
if (i < typeInfo.Column - 1)
{
tempSB.Append(", ");
}
}
sourceBuilder.Append($@"
public static {typeInfo.TypeFullName} identity => new {typeInfo.TypeFullName}({string.Join(", ", tempSB.ToString())});");
}
sourceBuilder.Append($@"
public static {typeInfo.TypeFullName} zero => default;");
sourceBuilder.AppendLine();
}
private void GenerateConstructors()
{
sourceBuilder.AppendLine($@"
public unsafe {typeInfo.TypeName}(in global::System.ReadOnlySpan<{typeInfo.ComponentTypeFullName}> values)
{{
if (values.Length < {typeInfo.Column})
{{
throw new global::System.ArgumentException($""Expected at least {typeInfo.Column} values, but got {{values.Length}}"", nameof(values));
}}
fixed ({typeInfo.TypeName}* pThis = &this)
fixed ({typeInfo.ComponentTypeFullName}* pValues = values)
{{
*pThis = *({typeInfo.TypeName}*)pValues;
}}
}}");
sourceBuilder.Append($@"
public {typeInfo.TypeName}({typeInfo.ComponentTypeFullName} value)
{{");
for (var i = 0; i < typeInfo.Column; i++)
{
sourceBuilder.Append($@"
this.{_matrixComponents[i]} = value;");
}
sourceBuilder.AppendLine(@"
}");
sourceBuilder.Append($@"
public {typeInfo.TypeName}({string.Join(", ", Enumerable.Range(0, typeInfo.Column).Select(i => $"{typeInfo.ComponentTypeFullName} c{i}"))})
{{");
for (var i = 0; i < typeInfo.Column; i++)
{
sourceBuilder.Append($@"
this.{_matrixComponents[i]} = c{i};");
}
sourceBuilder.Append(@"
}");
if (typeInfo.ElementTypeSymbol != null)
{
sourceBuilder.AppendLine($@"
public unsafe {typeInfo.TypeName}(in global::System.ReadOnlySpan<{typeInfo.ElementTypeFullName}> values)
{{
if (values.Length < {typeInfo.Column * typeInfo.Row})
{{
throw new global::System.ArgumentException($""Expected at least {typeInfo.Column * typeInfo.Row} values, but got {{values.Length}}"", nameof(values));
}}
fixed ({typeInfo.TypeName}* pThis = &this)
fixed ({typeInfo.ElementTypeFullName}* pValues = values)
{{
*pThis = *({typeInfo.TypeName}*)pValues;
}}
}}");
sourceBuilder.Append($@"
public {typeInfo.TypeName}({typeInfo.ElementTypeFullName} value)
{{");
for (var c = 0; c < typeInfo.Column; c++)
{
sourceBuilder.Append($@"
this.{_matrixComponents[c]} = value;");
}
sourceBuilder.AppendLine($@"
}}");
var tempSB = new StringBuilder();
for (var r = 0; r < typeInfo.Row; r++)
{
for (var c = 0; c < typeInfo.Column; c++)
{
tempSB.Append($"{typeInfo.ElementTypeFullName} m{r}{c}");
if (!(r == typeInfo.Row - 1 && c == typeInfo.Column - 1))
{
tempSB.Append(", ");
}
}
}
sourceBuilder.Append($@"
public {typeInfo.TypeName}({tempSB})
{{");
for (var c = 0; c < typeInfo.Column; c++)
{
sourceBuilder.Append($@"
this.{_matrixComponents[c]} = new({string.Join(", ", Enumerable.Range(0, typeInfo.Row).Select(r => $"m{r}{c}"))});");
}
sourceBuilder.AppendLine($@"
}}");
}
}
private void GenerateUnsafeMethod()
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public unsafe {typeInfo.ComponentTypeFullName}* AsPointer()
{{
return ({typeInfo.ComponentTypeFullName}*)global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref this);
}}
{INLINE_METHOD_ATTRIBUTE}
public unsafe global::System.Span<{typeInfo.ComponentTypeFullName}> AsSpan()
{{
return new global::System.Span<{typeInfo.ComponentTypeFullName}>(AsPointer(), {typeInfo.Column});
}}");
}
private void GenerateOverrideMethod()
{
var components = _matrixComponents.Take(typeInfo.Column).ToArray();
sourceBuilder.AppendLine($@"
public override readonly string ToString()
{{
return $""({string.Join(", ", components.Select(c => $"{c}: {{this.{c}}}"))})"";
}}
public override readonly int GetHashCode()
{{
return global::System.HashCode.Combine({string.Join(", ", components.Select(c => $"this.{c}"))});
}}
public override readonly bool Equals(object? obj)
{{
return obj is {typeInfo.TypeFullName} value && Equals(value);
}}
public readonly bool Equals({typeInfo.TypeFullName} other)
{{
return {string.Join(" && ", components.Select(c => $"this.{c}.Equals(other.{c})"))};
}}
public static global::Misaki.HighPerformance.Mathematics.bool{typeInfo.Row}x{typeInfo.Column} operator ==({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", components.Select(c => $"lhs.{c} == rhs.{c}"))});
}}
public static global::Misaki.HighPerformance.Mathematics.bool{typeInfo.Row}x{typeInfo.Column} operator !=({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", components.Select(c => $"lhs.{c} != rhs.{c}"))});
}}
public static implicit operator {typeInfo.TypeFullName}(global::System.ReadOnlySpan<{typeInfo.ComponentTypeFullName}> value)
{{
return new {typeInfo.TypeFullName}(value);
}}
public static implicit operator {typeInfo.TypeFullName}({typeInfo.ComponentTypeFullName} value)
{{
return new(value);
}}");
}
private void GenerateArithmeticOperators()
{
// Add
sourceBuilder.AppendLine($@"
public static {typeInfo.TypeFullName} operator +({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} + rhs.{c}"))});
}}
public static {typeInfo.TypeFullName} operator +({typeInfo.TypeFullName} lhs, {typeInfo.ComponentTypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} + rhs"))});
}}
public static {typeInfo.TypeFullName} operator +({typeInfo.ComponentTypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs + rhs.{c}"))});
}}");
// Subtract
sourceBuilder.AppendLine($@"
public static {typeInfo.TypeFullName} operator -({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} - rhs.{c}"))});
}}
public static {typeInfo.TypeFullName} operator -({typeInfo.TypeFullName} lhs, {typeInfo.ComponentTypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} - rhs"))});
}}
public static {typeInfo.TypeFullName} operator -({typeInfo.ComponentTypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs - rhs.{c}"))});
}}");
// Multiply
sourceBuilder.AppendLine($@"
public static {typeInfo.TypeFullName} operator *({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} * rhs.{c}"))});
}}
public static {typeInfo.TypeFullName} operator *({typeInfo.TypeFullName} lhs, {typeInfo.ComponentTypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} * rhs"))});
}}
public static {typeInfo.TypeFullName} operator *({typeInfo.ComponentTypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs * rhs.{c}"))});
}}");
// Divide
sourceBuilder.AppendLine($@"
public static {typeInfo.TypeFullName} operator /({typeInfo.ComponentTypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs / rhs.{c}"))});
}}
public static {typeInfo.TypeFullName} operator /({typeInfo.TypeFullName} lhs, {typeInfo.ComponentTypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} / rhs"))});
}}
public static {typeInfo.TypeFullName} operator /({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} / rhs.{c}"))});
}}");
// Modulus
sourceBuilder.AppendLine($@"
public static {typeInfo.TypeFullName} operator %({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} % rhs.{c}"))});
}}
public static {typeInfo.TypeFullName} operator %({typeInfo.TypeFullName} lhs, {typeInfo.ComponentTypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} % rhs"))});
}}
public static {typeInfo.TypeFullName} operator %({typeInfo.ComponentTypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs % rhs.{c}"))});
}}");
// Unary operators
sourceBuilder.AppendLine($@"
public static {typeInfo.TypeFullName} operator +({typeInfo.TypeFullName} value)
{{
return value;
}}
public static {typeInfo.TypeFullName} operator -({typeInfo.TypeFullName} value)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"-value.{c}"))});
}}
public static {typeInfo.TypeFullName} operator ++({typeInfo.TypeFullName} value)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"value.{c} + 1"))});
}}
public static {typeInfo.TypeFullName} operator --({typeInfo.TypeFullName} value)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"value.{c} - 1"))});
}}");
// Comparison operators
sourceBuilder.AppendLine($@"
public static global::Misaki.HighPerformance.Mathematics.bool{typeInfo.Row}x{typeInfo.Column} operator <({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} < rhs.{c}"))});
}}
public static global::Misaki.HighPerformance.Mathematics.bool{typeInfo.Row}x{typeInfo.Column} operator <=({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} <= rhs.{c}"))});
}}
public static global::Misaki.HighPerformance.Mathematics.bool{typeInfo.Row}x{typeInfo.Column} operator >({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} > rhs.{c}"))});
}}
public static global::Misaki.HighPerformance.Mathematics.bool{typeInfo.Row}x{typeInfo.Column} operator >=({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} >= rhs.{c}"))});
}}");
// Bitwise operators
sourceBuilder.AppendLine($@"
public static {typeInfo.TypeFullName} operator <<({typeInfo.TypeFullName} lhs, int shift)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} << shift"))});
}}
public static {typeInfo.TypeFullName} operator >>({typeInfo.TypeFullName} lhs, int shift)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} >> shift"))});
}}
public static {typeInfo.TypeFullName} operator &({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} & rhs.{c}"))});
}}
public static {typeInfo.TypeFullName} operator |({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} | rhs.{c}"))});
}}
public static {typeInfo.TypeFullName} operator ^({typeInfo.TypeFullName} lhs, {typeInfo.TypeFullName} rhs)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"lhs.{c} ^ rhs.{c}"))});
}}
public static {typeInfo.TypeFullName} operator ~({typeInfo.TypeFullName} value)
{{
return new({string.Join(", ", _matrixComponents.Take(typeInfo.Column).Select(c => $"~value.{c}"))});
}}");
}
private void GenerateMathMethod()
{
if (typeInfo.ElementTypeSymbol == null)
{
return;
}
sourceBuilder.Append($@"
public static partial class math
{{
public static {typeInfo.TypePrefix}{typeInfo.Column}x{typeInfo.Row} transpose({typeInfo.TypeFullName} value)
{{
return new {typeInfo.TypePrefix}{typeInfo.Column}x{typeInfo.Row}(");
for (var i = 0; i < typeInfo.Column; i++)
{
sourceBuilder.Append($@"
{string.Join(", ", _matrixComponents.Take(typeInfo.Row).Select((c, j) => $"value.{_matrixComponents[i]}.{_vectorComponents[j]}"))}");
if (i < typeInfo.Column - 1)
{
sourceBuilder.Append(",");
}
else
{
sourceBuilder.Append(");");
}
}
sourceBuilder.AppendLine($@"
}}");
if (typeInfo.Arithmetic)
{
if (typeInfo.Row == typeInfo.Column)
{
GenerateDeterminantMethod();
if (typeInfo.CanInverse)
{
if (typeInfo.Row == 2)
{
GenerateInverse2x2Method();
}
else if (typeInfo.Row == 3)
{
GenerateInverse3x3Method();
}
else if (typeInfo.Row == 4)
{
GenerateInverse4x4Method();
}
}
}
GenerateMulMethod();
}
sourceBuilder.Append($@"
}}");
}
private void GenerateInverse2x2Method()
{
sourceBuilder.AppendLine($@"
public static {typeInfo.TypeFullName} inverse({typeInfo.TypeFullName} value)
{{
var c0 = value.{_matrixComponents[0]};
var c1 = value.{_matrixComponents[1]};
// elements
var m00 = c0.x;
var m01 = c1.x;
var m10 = c0.y;
var m11 = c1.y;
var det = m00 * m11 - m01 * m10;
if (det == 0.0f)
{{
throw new System.InvalidOperationException(""Matrix is singular"");
}}
var invDet = 1.0f / det;
// adjugate: [ d -b; -c a ]
return new(new(m11 * invDet, -m10 * invDet), new(-m01 * invDet, m00 * invDet));
}}");
}
private void GenerateInverse3x3Method()
{
sourceBuilder.AppendLine($@"
public static {typeInfo.TypeFullName} inverse({typeInfo.TypeFullName} value)
{{
var c0 = value.{_matrixComponents[0]};
var c1 = value.{_matrixComponents[1]};
var c2 = value.{_matrixComponents[2]};
var a00 = c0.x;
var a01 = c1.x;
var a02 = c2.x;
var a10 = c0.y;
var a11 = c1.y;
var a12 = c2.y;
var a20 = c0.z;
var a21 = c1.z;
var a22 = c2.z;
// cofactors (adjugate transposed)
var c00 = a11 * a22 - a12 * a21;
var c01 = - (a10 * a22 - a12 * a20);
var c02 = a10 * a21 - a11 * a20;
var c10 = - (a01 * a22 - a02 * a21);
var c11 = a00 * a22 - a02 * a20;
var c12 = - (a00 * a21 - a01 * a20);
var c20 = a01 * a12 - a02 * a11;
var c21 = - (a00 * a12 - a02 * a10);
var c22 = a00 * a11 - a01 * a10;
var det = a00 * c00 + a01 * c01 + a02 * c02;
if (det == 0.0f)
{{
throw new System.InvalidOperationException(""Matrix is singular"");
}}
var invDet = 1.0f / det;
// adjugate is transpose of cofactor matrix (we computed cofactors already)
return new(new(c00 * invDet, c10 * invDet, c20 * invDet), new(c01 * invDet, c11 * invDet, c21 * invDet), new(c02 * invDet, c12 * invDet, c22 * invDet));
}}");
}
private void GenerateInverse4x4Method()
{
sourceBuilder.AppendLine($@"
public static {typeInfo.TypeFullName} inverse({typeInfo.TypeFullName} value)
{{
var c0 = value.{_matrixComponents[0]};
var c1 = value.{_matrixComponents[1]};
var c2 = value.{_matrixComponents[2]};
var c3 = value.{_matrixComponents[3]};
// movelh
var r0y_r1y_r0x_r1x = shuffle(c1, c0, ShuffleComponent.LeftX, ShuffleComponent.LeftY, ShuffleComponent.RightX, ShuffleComponent.RightY);
var r0z_r1z_r0w_r1w = shuffle(c2, c3, ShuffleComponent.LeftX, ShuffleComponent.LeftY, ShuffleComponent.RightX, ShuffleComponent.RightY);
// movehl
var r2y_r3y_r2x_r3x = shuffle(c1, c0, ShuffleComponent.LeftZ, ShuffleComponent.LeftW, ShuffleComponent.RightZ, ShuffleComponent.RightW);
var r2z_r3z_r2w_r3w = shuffle(c2, c3, ShuffleComponent.LeftZ, ShuffleComponent.LeftW, ShuffleComponent.RightZ, ShuffleComponent.RightW);
var r1y_r2y_r1x_r2x = shuffle(c1, c0, ShuffleComponent.LeftY, ShuffleComponent.LeftZ, ShuffleComponent.RightY, ShuffleComponent.RightZ);
var r1z_r2z_r1w_r2w = shuffle(c2, c3, ShuffleComponent.LeftY, ShuffleComponent.LeftZ, ShuffleComponent.RightY, ShuffleComponent.RightZ);
var r3y_r0y_r3x_r0x = shuffle(c1, c0, ShuffleComponent.LeftW, ShuffleComponent.LeftX, ShuffleComponent.RightW, ShuffleComponent.RightX);
var r3z_r0z_r3w_r0w = shuffle(c2, c3, ShuffleComponent.LeftW, ShuffleComponent.LeftX, ShuffleComponent.RightW, ShuffleComponent.RightX);
var r0_wzyx = shuffle(r0z_r1z_r0w_r1w, r0y_r1y_r0x_r1x, ShuffleComponent.LeftZ, ShuffleComponent.LeftX, ShuffleComponent.RightX, ShuffleComponent.RightZ);
var r1_wzyx = shuffle(r0z_r1z_r0w_r1w, r0y_r1y_r0x_r1x, ShuffleComponent.LeftW, ShuffleComponent.LeftY, ShuffleComponent.RightY, ShuffleComponent.RightW);
var r2_wzyx = shuffle(r2z_r3z_r2w_r3w, r2y_r3y_r2x_r3x, ShuffleComponent.LeftZ, ShuffleComponent.LeftX, ShuffleComponent.RightX, ShuffleComponent.RightZ);
var r3_wzyx = shuffle(r2z_r3z_r2w_r3w, r2y_r3y_r2x_r3x, ShuffleComponent.LeftW, ShuffleComponent.LeftY, ShuffleComponent.RightY, ShuffleComponent.RightW);
var r0_xyzw = shuffle(r0y_r1y_r0x_r1x, r0z_r1z_r0w_r1w, ShuffleComponent.LeftZ, ShuffleComponent.LeftX, ShuffleComponent.RightX, ShuffleComponent.RightZ);
// Calculate remaining inner term pairs. inner terms have zw=-xy, so we only have to calculate xy and can pack two pairs per vector.
var inner12_23 = r1y_r2y_r1x_r2x * r2z_r3z_r2w_r3w - r1z_r2z_r1w_r2w * r2y_r3y_r2x_r3x;
var inner02_13 = r0y_r1y_r0x_r1x * r2z_r3z_r2w_r3w - r0z_r1z_r0w_r1w * r2y_r3y_r2x_r3x;
var inner30_01 = r3z_r0z_r3w_r0w * r0y_r1y_r0x_r1x - r3y_r0y_r3x_r0x * r0z_r1z_r0w_r1w;
// Expand inner terms back to 4 components. zw signs still need to be flipped
var inner12 = shuffle(inner12_23, inner12_23, ShuffleComponent.LeftX, ShuffleComponent.LeftZ, ShuffleComponent.RightZ, ShuffleComponent.RightX);
var inner23 = shuffle(inner12_23, inner12_23, ShuffleComponent.LeftY, ShuffleComponent.LeftW, ShuffleComponent.RightW, ShuffleComponent.RightY);
var inner02 = shuffle(inner02_13, inner02_13, ShuffleComponent.LeftX, ShuffleComponent.LeftZ, ShuffleComponent.RightZ, ShuffleComponent.RightX);
var inner13 = shuffle(inner02_13, inner02_13, ShuffleComponent.LeftY, ShuffleComponent.LeftW, ShuffleComponent.RightW, ShuffleComponent.RightY);
// Calculate minors
var minors0 = r3_wzyx * inner12 - r2_wzyx * inner13 + r1_wzyx * inner23;
var denom = r0_xyzw * minors0;
// Horizontal sum of denominator. Free sign flip of z and w compensates for missing flip in inner terms.
denom = denom + shuffle(denom, denom, ShuffleComponent.LeftY, ShuffleComponent.LeftX, ShuffleComponent.RightW, ShuffleComponent.RightZ); // x+y x+y z+w z+w
denom = denom - shuffle(denom, denom, ShuffleComponent.LeftZ, ShuffleComponent.LeftZ, ShuffleComponent.RightX, ShuffleComponent.RightX); // x+y-z-w x+y-z-w z+w-x-y z+w-x-y
var rcp_denom_ppnn = 1 / denom;
{typeInfo.TypeFullName} res;
res.{_matrixComponents[0]} = minors0 * rcp_denom_ppnn;
var inner30 = shuffle(inner30_01, inner30_01, ShuffleComponent.LeftX, ShuffleComponent.LeftZ, ShuffleComponent.RightZ, ShuffleComponent.RightX);
var inner01 = shuffle(inner30_01, inner30_01, ShuffleComponent.LeftY, ShuffleComponent.LeftW, ShuffleComponent.RightW, ShuffleComponent.RightY);
var minors1 = r2_wzyx * inner30 - r0_wzyx * inner23 - r3_wzyx * inner02;
res.{_matrixComponents[1]} = minors1 * rcp_denom_ppnn;
var minors2 = r0_wzyx * inner13 - r1_wzyx * inner30 - r3_wzyx * inner01;
res.{_matrixComponents[2]} = minors2 * rcp_denom_ppnn;
var minors3 = r1_wzyx * inner02 - r0_wzyx * inner12 + r2_wzyx * inner01;
res.{_matrixComponents[3]} = minors3 * rcp_denom_ppnn;
return res;
}}
public static {typeInfo.TypeFullName} fastinverse({typeInfo.TypeFullName} m)
{{
var c0 = m.c0;
var c1 = m.c1;
var c2 = m.c2;
var pos = m.c3;
var zero = default({typeInfo.ComponentTypeFullName});
// unpacklo
var t0 = shuffle(c0, c2, ShuffleComponent.LeftX, ShuffleComponent.RightX, ShuffleComponent.LeftY, ShuffleComponent.RightY);
var t1 = shuffle(c1, zero, ShuffleComponent.LeftX, ShuffleComponent.RightX, ShuffleComponent.LeftY, ShuffleComponent.RightY);
// unpackhi
var t2 = shuffle(c0, c2, ShuffleComponent.LeftZ, ShuffleComponent.RightZ, ShuffleComponent.LeftW, ShuffleComponent.RightW);
var t3 = shuffle(c1, zero, ShuffleComponent.LeftZ, ShuffleComponent.RightZ, ShuffleComponent.LeftW, ShuffleComponent.RightW);
var r0 = shuffle(t0, t1, ShuffleComponent.LeftX, ShuffleComponent.RightX, ShuffleComponent.LeftY, ShuffleComponent.RightY);
var r1 = shuffle(t0, t1, ShuffleComponent.LeftZ, ShuffleComponent.RightZ, ShuffleComponent.LeftW, ShuffleComponent.RightW);
var r2 = shuffle(t2, t3, ShuffleComponent.LeftX, ShuffleComponent.RightX, ShuffleComponent.LeftY, ShuffleComponent.RightY);
pos = -(r0 * pos.x + r1 * pos.y + r2 * pos.z);
pos.w = 1.0f;
return new(r0, r1, r2, pos);
}}");
}
private void GenerateDeterminantMethod()
{
sourceBuilder.Append($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeInfo.ElementTypeFullName} determinant({typeInfo.TypeFullName} value)
{{");
for (var i = 0; i < typeInfo.Column; i++)
{
sourceBuilder.Append($@"
var {_matrixComponents[i]} = value.{_matrixComponents[i]};");
}
sourceBuilder.AppendLine();
string Elem(int r, int c) => $"{_matrixComponents[c]}.{_vectorComponents[r]}";
// recursive function that returns a string for determinant of submatrix defined by rowIndices and colIndices
string DetExpr(List<int> rows, List<int> cols)
{
var m = rows.Count;
if (m == 1)
{
return Elem(rows[0], cols[0]);
}
if (m == 2)
{
// a b
// c d -> a*d - b*c
var a = Elem(rows[0], cols[0]);
var b = Elem(rows[0], cols[1]);
var c = Elem(rows[1], cols[0]);
var d = Elem(rows[1], cols[1]);
return $"({a} * {d} - {b} * {c})";
}
// expand along the first row (rows[0])
var sb = new StringBuilder();
for (var j = 0; j < m; ++j)
{
var col = cols[j];
var aij = Elem(rows[0], col);
// build minor indices
var subRows = new List<int>(rows.Skip(1));
var subCols = new List<int>(cols.Where((_, idx) => idx != j));
var subDet = DetExpr(subRows, subCols);
var sign = ((0 + j) % 2 == 0) ? 1 : -1;
if (sign == 1)
sb.Append($"{aij} * {subDet}");
else
sb.Append($"-({aij} * {subDet})");
if (j != m - 1)
sb.Append(" + ");
}
return $"({sb})";
}
var rowsList = Enumerable.Range(0, typeInfo.Row).ToList();
var colsList = Enumerable.Range(0, typeInfo.Row).ToList();
var expr = DetExpr(rowsList, colsList);
sourceBuilder.AppendLine($@"
return ({typeInfo.ElementTypeFullName}){expr};
}}");
}
private void GenerateMulMethod()
{
var lhsType = typeInfo.TypeFullName;
var lhsRows = typeInfo.Row;
var lhsCols = typeInfo.Column;
var typePrefix = typeInfo.TypePrefix;
// Matrix-Vector Multiplication: RxC * C-element vector = R-element vector
var rhsVectorType = $"{typePrefix}{lhsCols}";
var resultVectorType = $"{typePrefix}{lhsRows}";
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {resultVectorType} mul({lhsType} m, {rhsVectorType} v)
{{
return {string.Join(" + ", Enumerable.Range(0, lhsCols).Select(c => $"m.{_matrixComponents[c]} * v.{_vectorComponents[c]}"))};
}}");
// Vector-Matrix Multiplication: R-element vector * RxC = C-element vector
var lhsVectorType = $"{typePrefix}{lhsRows}";
var resultVectorTypeT = $"{typePrefix}{lhsCols}";
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {resultVectorTypeT} mul({lhsVectorType} v, {lhsType} m)
{{
return new {resultVectorTypeT}({string.Join(", ", Enumerable.Range(0, lhsCols).Select(c => $"dot(v, m.{_matrixComponents[c]})"))});
}}");
// Matrix-Matrix Multiplication
for (var rhsRows = 2; rhsRows <= 4; rhsRows++)
{
if (lhsCols != rhsRows)
{
continue;
}
for (var rhsCols = 2; rhsCols <= 4; rhsCols++)
{
var rhsType = $"{typePrefix}{rhsRows}x{rhsCols}";
var resultType = $"{typePrefix}{lhsRows}x{rhsCols}";
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {resultType} mul({lhsType} lhs, {rhsType} rhs)
{{
return new {resultType}({string.Join(", ", Enumerable.Range(0, rhsCols).Select(c => $"mul(lhs, rhs.{_matrixComponents[c]})"))});
}}");
}
}
}
}
}

View File

@@ -0,0 +1,694 @@
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
namespace Misaki.HighPerformance.Mathematics.CodeGen.Generators
{
internal class VectorGenerator : GeneratorBase
{
private readonly string[] _vectorComponents = new[] { "x", "y", "z", "w" };
private int _vectorBitsSize;
private int _missingComponents;
private readonly List<(string signature, string assignment)> _constructorSignatures = new();
protected override void Initialize()
{
var componentSize = typeInfo.ComponentSize;
var typeSize = componentSize * typeInfo.Row * typeInfo.Column;
var vectorBytesSize = typeSize switch
{
<= 16 => 16,
<= 32 => 32,
_ => 64,
};
_vectorBitsSize = vectorBytesSize * 8;
_missingComponents = (vectorBytesSize - typeSize) / componentSize;
}
protected override void GenerateBody()
{
GenerateField();
if (typeInfo.Arithmetic)
{
GenerateUnitVector();
}
GenerateConstructors();
GenerateUnsafeMethod();
GenerateOverrideMethod();
if (typeInfo.Arithmetic)
{
GenerateArithmeticOperators();
}
if (!string.IsNullOrEmpty(typeInfo.TypePrefix))
{
GenerateSwizzleProperties();
}
}
protected override void GenerateTypeEnd()
{
base.GenerateTypeEnd();
if (typeInfo.Arithmetic)
{
sourceBuilder.AppendLine();
GenerateVectorExtension();
GenerateMathMethod();
}
}
private void GenerateField()
{
for (var i = 0; i < typeInfo.Row; i++)
{
sourceBuilder.Append($@"
public {typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)} {_vectorComponents[i]};");
}
sourceBuilder.AppendLine();
sourceBuilder.AppendLine($@"
public unsafe ref {typeInfo.ComponentTypeFullName} this[int index]
{{
{INLINE_METHOD_ATTRIBUTE}
get => ref (({typeInfo.ComponentTypeFullName}*)global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref this))[index];
}}");
}
private static List<List<int>> GetPartitions(int target)
{
var result = new List<List<int>>();
void Recurse(List<int> current, int sum)
{
if (sum == target)
{
result.Add(new List<int>(current));
return;
}
for (var i = 1; i <= 3; i++)
{
if (sum + i <= target)
{
current.Add(i);
Recurse(current, sum + i);
current.RemoveAt(current.Count - 1);
}
}
}
Recurse(new(), 0);
return result;
}
private static IEnumerable<List<int>> GetPermutations(List<int> list)
{
if (list.Count == 1)
yield return new List<int>(list);
else
{
var seen = new HashSet<string>();
for (var i = 0; i < list.Count; i++)
{
var head = list[i];
var tail = new List<int>(list);
tail.RemoveAt(i);
foreach (var perm in GetPermutations(tail))
{
perm.Insert(0, head);
var key = string.Join(",", perm);
if (seen.Add(key))
yield return perm;
}
}
}
}
private void GenerateUnitVector()
{
var typeFullName = typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
sourceBuilder.Append($@"
public static {typeFullName} one => new {typeFullName}({string.Join(", ", Enumerable.Repeat("1", typeInfo.Row))});");
sourceBuilder.Append($@"
public static {typeFullName} zero => default;");
sourceBuilder.Append($@"
public static {typeFullName} unitX => new {typeFullName}({string.Join(", ", Enumerable.Repeat("0", typeInfo.Row - 1).Prepend("1"))});");
sourceBuilder.Append($@"
public static {typeFullName} unitY => new {typeFullName}({string.Join(", ", Enumerable.Repeat("0", typeInfo.Row - 2).Prepend("0, 1"))});");
if (typeInfo.Row > 2)
{
sourceBuilder.Append($@"
public static {typeFullName} unitZ => new {typeFullName}({string.Join(", ", Enumerable.Repeat("0", typeInfo.Row - 3).Prepend("0, 0, 1"))});");
}
if (typeInfo.Row > 3)
{
sourceBuilder.Append($@"
public static {typeFullName} unitW => new {typeFullName}({string.Join(", ", Enumerable.Repeat("0", typeInfo.Row - 4).Prepend("0, 0, 0, 1"))});");
}
sourceBuilder.AppendLine();
}
private void GenerateConstructors()
{
var typeName = typeInfo.TypeName;
var componentType = typeInfo.ComponentTypeFullName;
sourceBuilder.AppendLine($@"
public unsafe {typeName}(global::System.ReadOnlySpan<{componentType}> values)
{{
if (values.Length < {typeInfo.Row})
{{
throw new global::System.ArgumentException($""Expected at least {typeInfo.Row} values, but got {{values.Length}}"", nameof(values));
}}
fixed ({typeName}* pThis = &this)
fixed ({componentType}* pValues = values)
{{
*pThis = *({typeName}*)pValues;
}}
}}");
sourceBuilder.AppendLine($@"
public {typeName}({componentType} value)
{{
this = Create({string.Join(", ", Enumerable.Range(0, typeInfo.Row).Select(_ => "value"))});
}}");
if (string.IsNullOrEmpty(typeInfo.TypePrefix))
{
return;
}
var partitions = GetPartitions(typeInfo.Row);
var seenSignatures = new HashSet<string>();
foreach (var partition in partitions)
{
foreach (var perm in GetPermutations(partition))
{
var paramNames = new List<string>();
var paramList = new List<string>();
var assignments = new List<string>();
var fieldOffset = 0;
for (var i = 0; i < perm.Count; i++)
{
var size = perm[i];
var type = size == 1 ? componentType : $"{typeInfo.TypePrefix}{size}";
var name = string.Empty;
for (var j = 0; j < size; j++)
{
name += $"{_vectorComponents[fieldOffset + j]}";
}
paramNames.Add(name);
paramList.Add($"{type} {name}");
for (var j = 0; j < size; j++)
{
var source = size == 1 ? name : $"{name}.{_vectorComponents[j]}";
assignments.Add(source);
}
fieldOffset += size;
}
var signature = string.Join(", ", paramList);
if (!seenSignatures.Add(signature))
continue;
var assignment = string.Join(", ", assignments);
_constructorSignatures.Add((signature, assignment));
}
}
foreach (var (signature, assignment) in _constructorSignatures)
{
sourceBuilder.AppendLine($@"
public {typeName}({signature})
{{
this = Create({assignment});
}}");
}
sourceBuilder.Append($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} Create({string.Join(", ", Enumerable.Range(0, typeInfo.Row).Select((_, i) => $"{typeInfo.ComponentTypeFullName} {_vectorComponents[i]}"))})
{{");
if (typeInfo.VectorType != null)
{
sourceBuilder.Append($@"
global::System.Span<{typeInfo.ComponentTypeFullName}> span = [{string.Join(", ", Enumerable.Range(0, typeInfo.Row + _missingComponents).Select(i => i < typeInfo.Row ? $"{_vectorComponents[i]}" : "default"))}];
var vector = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create(global::System.Runtime.CompilerServices.Unsafe.As<global::System.Span<{typeInfo.ComponentTypeFullName}>, global::System.ReadOnlySpan<{typeInfo.VectorTypeFullName}>>(ref span));");
}
else
{
sourceBuilder.Append($@"
var vector = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create({string.Join(", ", Enumerable.Range(0, typeInfo.Row + _missingComponents).Select(i => i < typeInfo.Row ? $"{_vectorComponents[i]}" : "default"))});");
}
sourceBuilder.AppendLine($@"
ref var address = ref global::System.Runtime.CompilerServices.Unsafe.As<global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{typeInfo.VectorTypeFullName}>, byte>(ref vector);
return global::System.Runtime.CompilerServices.Unsafe.ReadUnaligned<{typeInfo.TypeFullName}>(ref address);
}}");
}
private void GenerateUnsafeMethod()
{
var componentType = typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public unsafe {componentType}* AsPointer()
{{
return ({componentType}*)global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref this);
}}
{INLINE_METHOD_ATTRIBUTE}
public unsafe global::System.Span<{componentType}> AsSpan()
{{
return new global::System.Span<{componentType}>(AsPointer(), {typeInfo.Row});
}}");
}
private void GenerateOverrideMethod()
{
var typeName = typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var componentType = typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var components = _vectorComponents.Take(typeInfo.Row).ToArray();
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public override readonly string ToString()
{{
return $""({string.Join(", ", components.Select(c => $"{c}: {{this.{c}}}"))})"";
}}
{INLINE_METHOD_ATTRIBUTE}
public override readonly int GetHashCode()
{{
return global::System.HashCode.Combine({string.Join(", ", components.Select(c => $"this.{c}"))});
}}
{INLINE_METHOD_ATTRIBUTE}
public override readonly bool Equals(object? obj)
{{
return obj is {typeName} value && Equals(value);
}}
{INLINE_METHOD_ATTRIBUTE}
public readonly bool Equals({typeName} other)
{{
return {string.Join(" && ", components.Select(c => $"this.{c}.Equals(other.{c})"))};
}}
{INLINE_METHOD_ATTRIBUTE}
public static global::Misaki.HighPerformance.Mathematics.bool{typeInfo.Row} operator ==({typeName} lhs, {typeName} rhs)
{{
return new({string.Join(", ", components.Select(c => $"lhs.{c} == rhs.{c}"))});
}}
{INLINE_METHOD_ATTRIBUTE}
public static global::Misaki.HighPerformance.Mathematics.bool{typeInfo.Row} operator !=({typeName} lhs, {typeName} rhs)
{{
return new({string.Join(", ", components.Select(c => $"lhs.{c} != rhs.{c}"))});
}}
{INLINE_METHOD_ATTRIBUTE}
public static implicit operator {typeName}(global::System.ReadOnlySpan<{componentType}> value)
{{
return new {typeName}(value);
}}
{INLINE_METHOD_ATTRIBUTE}
public static implicit operator {typeName}({componentType} value)
{{
return new(value);
}}");
}
private void GenerateArithmeticOperators()
{
var typeName = typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var typeSimpleName = typeInfo.TypeSymbol.Name;
var componentType = typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var asResult = $"As{typeSimpleName}()";
// Add
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator +({typeName} lhs, {typeName} rhs)
{{
var vector = lhs.AsVector{_vectorBitsSize}() + rhs.AsVector{_vectorBitsSize}();
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator +({typeName} lhs, {componentType} rhs)
{{
return lhs + new {typeName}(rhs);
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator +({componentType} lhs, {typeName} rhs)
{{
return new {typeName}(lhs) + rhs;
}}");
// Subtract
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator -({typeName} lhs, {typeName} rhs)
{{
var vector = lhs.AsVector{_vectorBitsSize}() - rhs.AsVector{_vectorBitsSize}();
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator -({typeName} lhs, {componentType} rhs)
{{
return lhs - new {typeName}(rhs);
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator -({componentType} lhs, {typeName} rhs)
{{
return new {typeName}(lhs) - rhs;
}}");
// Multiply
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator *({typeName} lhs, {typeName} rhs)
{{
var vector = lhs.AsVector{_vectorBitsSize}() * rhs.AsVector{_vectorBitsSize}();
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator *({typeName} lhs, {componentType} rhs)
{{
var vector = lhs.AsVector{_vectorBitsSize}() * rhs;
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator *({componentType} lhs, {typeName} rhs)
{{
return new {typeName}(lhs) * rhs;
}}");
// Divide
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator /({typeName} lhs, {typeName} rhs)
{{
var vector = lhs.AsVector{_vectorBitsSize}() / rhs.AsVector{_vectorBitsSize}();
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator /({typeName} lhs, {componentType} rhs)
{{
var vector = lhs.AsVector{_vectorBitsSize}() / rhs;
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator /({componentType} lhs, {typeName} rhs)
{{
return new {typeName}(lhs) / rhs;
}}");
// Modulus
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator %({typeName} lhs, {typeName} rhs)
{{
return new {typeName}({string.Join(", ", _vectorComponents.Take(typeInfo.Row).Select(c => $"lhs.{c} % rhs.{c}"))});
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator %({typeName} lhs, {componentType} rhs)
{{
return lhs % new {typeName}(rhs);
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator %({componentType} lhs, {typeName} rhs)
{{
return new {typeName}(lhs) % rhs;
}}");
// Unary operators
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator +({typeName} value)
{{
return value;
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator -({typeName} value)
{{
var vector = -value.AsVector{_vectorBitsSize}();
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator ++({typeName} value)
{{
var vector = value.AsVector{_vectorBitsSize}() + global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create<{componentType}>(1);
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator --({typeName} value)
{{
var vector = value.AsVector{_vectorBitsSize}() - global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create<{componentType}>(1);
return vector.{asResult};
}}");
// Comparison operators
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static bool{typeInfo.Row} operator <({typeName} lhs, {typeName} rhs)
{{
var vector = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.LessThan(lhs.AsVector{_vectorBitsSize}(), rhs.AsVector{_vectorBitsSize}());
return new({string.Join(", ", _vectorComponents.Take(typeInfo.Row).Select((_, i) => $"vector[{i}] != 0"))});
}}
{INLINE_METHOD_ATTRIBUTE}
public static bool{typeInfo.Row} operator <=({typeName} lhs, {typeName} rhs)
{{
var vector = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.LessThanOrEqual(lhs.AsVector{_vectorBitsSize}(), rhs.AsVector{_vectorBitsSize}());
return new({string.Join(", ", _vectorComponents.Take(typeInfo.Row).Select((_, i) => $"vector[{i}] != 0"))});
}}
{INLINE_METHOD_ATTRIBUTE}
public static bool{typeInfo.Row} operator >({typeName} lhs, {typeName} rhs)
{{
var vector = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.GreaterThan(lhs.AsVector{_vectorBitsSize}(), rhs.AsVector{_vectorBitsSize}());
return new({string.Join(", ", _vectorComponents.Take(typeInfo.Row).Select((_, i) => $"vector[{i}] != 0"))});
}}
{INLINE_METHOD_ATTRIBUTE}
public static bool{typeInfo.Row} operator >=({typeName} lhs, {typeName} rhs)
{{
var vector = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.GreaterThanOrEqual(lhs.AsVector{_vectorBitsSize}(), rhs.AsVector{_vectorBitsSize}());
return new({string.Join(", ", _vectorComponents.Take(typeInfo.Row).Select((_, i) => $"vector[{i}] != 0"))});
}}");
// Bitwise operators
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator <<({typeName} x, int n)
{{
var vector = x.AsVector{_vectorBitsSize}() << n;
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator >>({typeName} x, int n)
{{
var vector = x.AsVector{_vectorBitsSize}() >> n;
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator &({typeName} lhs, {typeName} rhs)
{{
var vector = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.BitwiseAnd(lhs.AsVector{_vectorBitsSize}(), rhs.AsVector{_vectorBitsSize}());
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator |({typeName} lhs, {typeName} rhs)
{{
var vector = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.BitwiseOr(lhs.AsVector{_vectorBitsSize}(), rhs.AsVector{_vectorBitsSize}());
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator ^({typeName} lhs, {typeName} rhs)
{{
var vector = global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Xor(lhs.AsVector{_vectorBitsSize}(), rhs.AsVector{_vectorBitsSize}());
return vector.{asResult};
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} operator ~({typeName} value)
{{
unchecked
{{
return value ^ new {typeName}(({componentType})-1);
}}
}}");
}
private IEnumerable<string> GenerateSwizzles(IEnumerable<string> pool, int maxLen)
{
IEnumerable<string> Recurse(string prefix, int depth)
{
if (depth == 0)
{
yield return prefix;
}
else
{
foreach (var c in pool)
{
foreach (var s in Recurse(prefix + c, depth - 1))
{
yield return s;
}
}
}
}
return Enumerable.Range(2, maxLen - 1).SelectMany(len => Recurse(string.Empty, len));
}
private void GenerateSwizzleProperties()
{
var validComponents = _vectorComponents.Take(typeInfo.Row).ToArray();
var swizzles = GenerateSwizzles(validComponents, _vectorComponents.Length);
foreach (var swizzle in swizzles)
{
var targetDim = swizzle.Length;
var targetStruct = $"{typeInfo.TypePrefix}{targetDim}";
sourceBuilder.AppendLine($@"
public readonly {targetStruct} {swizzle}
{{
{INLINE_METHOD_ATTRIBUTE}
get => new({string.Join(", ", swizzle.Select(c => $"this.{c}"))});
}}");
}
}
private void GenerateVectorExtension()
{
var typeName = typeInfo.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var typeSimpleName = typeInfo.TypeSymbol.Name;
var componentType = typeInfo.ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
sourceBuilder.Append($@"
public static partial class VectorInterop
{{
{INLINE_METHOD_ATTRIBUTE}
public static global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}> AsVector{_vectorBitsSize}(this {typeName} value)
{{");
var fullTypeName = $"{typeInfo.TypePrefix}{typeInfo.Row + _missingComponents}";
sourceBuilder.Append($@"
return global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}.Create({string.Join(", ", Enumerable.Range(0, typeInfo.Row + _missingComponents).Select(i => i < typeInfo.Row ? $"value.{_vectorComponents[i]}" : "1"))});
}}
{INLINE_METHOD_ATTRIBUTE}
public static {typeName} As{typeSimpleName}(this global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}> value)
{{");
sourceBuilder.AppendLine($@"
ref var address = ref global::System.Runtime.CompilerServices.Unsafe.As<global::System.Runtime.Intrinsics.Vector{_vectorBitsSize}<{componentType}>, byte>(ref value);
return global::System.Runtime.CompilerServices.Unsafe.ReadUnaligned<{typeName}>(ref address);
}}
}}");
}
private void GenerateMathMethod()
{
var typeFullName = typeInfo.TypeFullName;
var typePrefix = typeInfo.TypePrefix;
var componentTypeFullName = typeInfo.ComponentTypeFullName;
sourceBuilder.Append($@"
public static partial class math
{{");
sourceBuilder.AppendLine($@"
public static {typeFullName} {typeInfo.TypeName}({componentTypeFullName} value)
{{
return {typeFullName}.Create({string.Join(", ", Enumerable.Range(0, typeInfo.Row).Select(_ => "value"))});
}}");
foreach (var (signature, assignment) in _constructorSignatures)
{
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typeFullName} {typeInfo.TypeName}({signature})
{{
return {typeFullName}.Create({assignment});
}}");
}
sourceBuilder.AppendLine($@"
{INLINE_METHOD_ATTRIBUTE}
public static {componentTypeFullName} shuffle({typeFullName} left, {typeFullName} right, ShuffleComponent x)
{{
return select_shuffle_component(left, right, x);
}}");
for (var i = 1; i < typeInfo.Row; i++)
{
var dimension = i + 1;
sourceBuilder.Append($@"
{INLINE_METHOD_ATTRIBUTE}
public static {typePrefix}{dimension} shuffle({typeFullName} left, {typeFullName} right, {string.Join(", ", Enumerable.Range(0, dimension).Select(x => $"ShuffleComponent {_vectorComponents[x]}"))})
{{
return new {typePrefix}{dimension}(");
for (var j = 0; j < dimension; j++)
{
sourceBuilder.Append($@"
select_shuffle_component(left, right, {_vectorComponents[j]})");
if (j < dimension - 1)
{
sourceBuilder.Append(",");
}
else
{
sourceBuilder.Append(");");
}
}
sourceBuilder.AppendLine($@"
}}");
}
sourceBuilder.Append($@"
{INLINE_METHOD_ATTRIBUTE}
internal static {componentTypeFullName} select_shuffle_component({typeFullName} a, {typeFullName} b, ShuffleComponent component)
{{
switch(component)
{{");
for (var i = 0; i < typeInfo.Row; i++)
{
sourceBuilder.Append($@"
case ShuffleComponent.Left{_vectorComponents[i].ToUpper()}:
return a.{_vectorComponents[i]};
case ShuffleComponent.Right{_vectorComponents[i].ToUpper()}:
return b.{_vectorComponents[i]};");
}
sourceBuilder.Append($@"
default:
throw new System.ArgumentException(""Invalid shuffle component: "" + component);
}}
}}
}}");
}
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,80 @@
using Microsoft.CodeAnalysis;
namespace Misaki.HighPerformance.Mathematics.CodeGen.Models
{
public record NumericTypeInfo
{
public INamedTypeSymbol TypeSymbol
{
get;
}
public INamedTypeSymbol ComponentTypeSymbol
{
get;
}
public int ComponentSize
{
get;
}
public int Row
{
get;
}
public int Column
{
get;
}
public string TypePrefix
{
get;
}
public bool Arithmetic
{
get;
}
public bool CanInverse
{
get;
}
public INamedTypeSymbol? ElementTypeSymbol
{
get;
}
public INamedTypeSymbol? VectorType
{
get;
}
public string TypeName => TypeSymbol.Name;
public string ComponentTypeName => ComponentTypeSymbol.Name;
public string TypeFullName => TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
public string ComponentTypeFullName => ComponentTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
public string? ElementTypeName => ElementTypeSymbol?.Name;
public string? ElementTypeFullName => ElementTypeSymbol?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
public string VectorTypeFullName => VectorType?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) ?? ComponentTypeFullName;
public NumericTypeInfo(INamedTypeSymbol typeSymbol, INamedTypeSymbol componentType, int componentSize, int row, int column, string typePrefix,
bool arithmetic, bool canInverse, INamedTypeSymbol? elementType, INamedTypeSymbol? vectorType)
{
TypeSymbol = typeSymbol;
ComponentTypeSymbol = componentType;
ComponentSize = componentSize;
Row = row;
Column = column;
TypePrefix = typePrefix;
Arithmetic = arithmetic;
CanInverse = canInverse;
ElementTypeSymbol = elementType;
VectorType = vectorType;
}
}
}

View File

@@ -0,0 +1,79 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Misaki.HighPerformance.Mathematics.CodeGen.Generators;
using Misaki.HighPerformance.Mathematics.CodeGen.Models;
using System.Linq;
namespace Misaki.HighPerformance.Mathematics.CodeGen
{
[Generator]
internal class NumericTypeGenerator : IIncrementalGenerator
{
private const string _TARGET_ATTRIBUTE_NAME = "Misaki.HighPerformance.Mathematics.Attributes.NumericTypeAttribute";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Create a provider that finds all types with NumericTypeAttribute
var typesWithAttribute = context.SyntaxProvider
.ForAttributeWithMetadataName(
fullyQualifiedMetadataName: _TARGET_ATTRIBUTE_NAME,
predicate: static (node, _) => node is ClassDeclarationSyntax or StructDeclarationSyntax,
transform: static (context, _) => GetTypeInfo(context))
.Where(static typeInfo => typeInfo is not null);
// Register the source output
context.RegisterSourceOutput(typesWithAttribute.Collect(), (spc, types) =>
{
foreach (var typeInfo in types)
{
if (typeInfo is null)
continue;
var generator = GetGenerator(typeInfo.Column);
var source = generator.Generate(typeInfo);
spc.AddSource($"{typeInfo.TypeSymbol.Name}.g.cs", source);
}
});
}
private static GeneratorBase GetGenerator(int column)
{
return column switch
{
1 => new VectorGenerator(),
_ => new MatrixGenerator(),
};
}
private static NumericTypeInfo? GetTypeInfo(GeneratorAttributeSyntaxContext context)
{
if (context.TargetSymbol is not INamedTypeSymbol typeSymbol)
{
return null;
}
// Get the attribute data
var attribute = typeSymbol.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == _TARGET_ATTRIBUTE_NAME);
if (attribute == null)
{
return null;
}
var index = 0;
var componentType = (INamedTypeSymbol)attribute.ConstructorArguments[index++].Value!;
var componentSize = (int)attribute.ConstructorArguments[index++].Value!;
var row = (int)attribute.ConstructorArguments[index++].Value!;
var column = (int)attribute.ConstructorArguments[index++].Value!;
var typePrefix = (string)attribute.ConstructorArguments[index++].Value!;
var arithmetic = (bool)attribute.ConstructorArguments[index++].Value!;
var canInverse = (bool)attribute.ConstructorArguments[index++].Value!;
var elementType = (INamedTypeSymbol?)attribute.ConstructorArguments[index++].Value;
var vectorType = (INamedTypeSymbol?)attribute.ConstructorArguments.ElementAtOrDefault(index++).Value;
return new NumericTypeInfo(typeSymbol, componentType, componentSize, row, column, typePrefix, arithmetic, canInverse, elementType, vectorType);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Misaki.HighPerformance.Mathematics.Attributes;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
internal class NumericTypeAttribute : Attribute
{
public NumericTypeAttribute(Type componentType, int componentSize, int row, int column, string typePrefix, bool arithmetic = true, bool canInverse = true, Type? elementType = default, Type? vectorType = default)
{
}
}

View File

@@ -4,48 +4,15 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Update="float4.tt"> <ProjectReference Include="..\Misaki.HighPerformance.Mathematics.CodeGen\Misaki.HighPerformance.Mathematics.CodeGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<LastGenOutput>float4.gen.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="float3.tt">
<LastGenOutput>float3.gen.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="float2.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>float2.gen.cs</LastGenOutput>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" /> <Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="Float.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Float.tt</DependentUpon>
</Compile>
<Compile Update="float2.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>float2.tt</DependentUpon>
</Compile>
<Compile Update="float3.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>float3.tt</DependentUpon>
</Compile>
<Compile Update="float4.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>float4.tt</DependentUpon>
</Compile>
</ItemGroup>
</Project> </Project>

View File

@@ -1,29 +0,0 @@
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#+
public int[] dimensions = new[] { 2, 3, 4 };
public IEnumerable<string> GenerateSwizzles(string[] pool, int maxLen)
{
IEnumerable<string> Recurse(string prefix, int depth)
{
if (depth == 0)
{
yield return prefix;
}
else
{
foreach (var c in pool)
{
foreach (var s in Recurse(prefix + c, depth - 1))
{
yield return s;
}
}
}
}
return Enumerable.Range(2, maxLen - 1).SelectMany(len => Recurse("", len));
}
#>

View File

@@ -1,49 +0,0 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace Misaki.HighPerformance.Mathematics;
internal static class Vectorize
{
public static Vector128<float> AsVector128(this float2 value)
{
Unsafe.SkipInit(out Vector128<float> result);
Unsafe.WriteUnaligned(ref Unsafe.As<Vector128<float>, byte>(ref result), value);
return result;
}
public static Vector128<float> AsVector128(this float3 value)
{
Unsafe.SkipInit(out Vector128<float> result);
Unsafe.WriteUnaligned(ref Unsafe.As<Vector128<float>, byte>(ref result), value);
return result;
}
public static Vector128<float> AsVector128(this float4 value)
{
Unsafe.SkipInit(out Vector128<float> result);
Unsafe.WriteUnaligned(ref Unsafe.As<Vector128<float>, byte>(ref result), value);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 AsFloat2(this Vector128<float> value)
{
ref var address = ref Unsafe.As<Vector128<float>, byte>(ref value);
return Unsafe.ReadUnaligned<float2>(ref address);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float3 AsFloat3(this Vector128<float> value)
{
ref var address = ref Unsafe.As<Vector128<float>, byte>(ref value);
return Unsafe.ReadUnaligned<float3>(ref address);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float4 AsFloat4(this Vector128<float> value)
{
ref var address = ref Unsafe.As<Vector128<float>, byte>(ref value);
return Unsafe.ReadUnaligned<float4>(ref address);
}
}

View File

@@ -0,0 +1,63 @@
using Misaki.HighPerformance.Mathematics.Attributes;
namespace Misaki.HighPerformance.Mathematics;
[NumericType(typeof(bool), sizeof(bool), 2, 1, "global::Misaki.HighPerformance.Mathematics.bool", false, vectorType: typeof(byte))]
public partial struct bool2
{
}
[NumericType(typeof(bool2), sizeof(bool), 2, 2, "global::Misaki.HighPerformance.Mathematics.bool", false, elementType: typeof(bool))]
public partial struct bool2x2
{
}
[NumericType(typeof(bool2), sizeof(bool), 2, 3, "global::Misaki.HighPerformance.Mathematics.bool", false, elementType: typeof(bool))]
public partial struct bool2x3
{
}
[NumericType(typeof(bool2), sizeof(bool), 2, 4, "global::Misaki.HighPerformance.Mathematics.bool", false, elementType: typeof(bool))]
public partial struct bool2x4
{
}
[NumericType(typeof(bool), sizeof(bool), 3, 1, "global::Misaki.HighPerformance.Mathematics.bool", false, vectorType: typeof(byte))]
public partial struct bool3
{
}
[NumericType(typeof(bool3), sizeof(bool), 3, 2, "global::Misaki.HighPerformance.Mathematics.bool", false, elementType: typeof(bool))]
public partial struct bool3x2
{
}
[NumericType(typeof(bool3), sizeof(bool), 3, 3, "global::Misaki.HighPerformance.Mathematics.bool", false, elementType: typeof(bool))]
public partial struct bool3x3
{
}
[NumericType(typeof(bool3), sizeof(bool), 3, 4, "global::Misaki.HighPerformance.Mathematics.bool", false, elementType: typeof(bool))]
public partial struct bool3x4
{
}
[NumericType(typeof(bool), sizeof(bool), 4, 1, "global::Misaki.HighPerformance.Mathematics.bool", false, vectorType: typeof(byte))]
public partial struct bool4
{
}
[NumericType(typeof(bool4), sizeof(bool), 4, 2, "global::Misaki.HighPerformance.Mathematics.bool", false, elementType: typeof(bool))]
public partial struct bool4x2
{
}
[NumericType(typeof(bool4), sizeof(bool), 4, 3, "global::Misaki.HighPerformance.Mathematics.bool", false, elementType: typeof(bool))]
public partial struct bool4x3
{
}
[NumericType(typeof(bool4), sizeof(bool), 4, 4, "global::Misaki.HighPerformance.Mathematics.bool", false, elementType: typeof(bool))]
public partial struct bool4x4
{
}

View File

@@ -0,0 +1,63 @@
using Misaki.HighPerformance.Mathematics.Attributes;
namespace Misaki.HighPerformance.Mathematics;
[NumericType(typeof(double), sizeof(double), 2, 1, "global::Misaki.HighPerformance.Mathematics.double")]
public partial struct double2
{
}
[NumericType(typeof(double2), sizeof(double), 2, 2, "global::Misaki.HighPerformance.Mathematics.double", elementType: typeof(double))]
public partial struct double2x2
{
}
[NumericType(typeof(double2), sizeof(double), 2, 3, "global::Misaki.HighPerformance.Mathematics.double", elementType: typeof(double))]
public partial struct double2x3
{
}
[NumericType(typeof(double2), sizeof(double), 2, 4, "global::Misaki.HighPerformance.Mathematics.double", elementType: typeof(double))]
public partial struct double2x4
{
}
[NumericType(typeof(double), sizeof(double), 3, 1, "global::Misaki.HighPerformance.Mathematics.double")]
public partial struct double3
{
}
[NumericType(typeof(double3), sizeof(double), 3, 2, "global::Misaki.HighPerformance.Mathematics.double", elementType: typeof(double))]
public partial struct double3x2
{
}
[NumericType(typeof(double3), sizeof(double), 3, 3, "global::Misaki.HighPerformance.Mathematics.double", elementType: typeof(double))]
public partial struct double3x3
{
}
[NumericType(typeof(double3), sizeof(double), 3, 4, "global::Misaki.HighPerformance.Mathematics.double", elementType: typeof(double))]
public partial struct double3x4
{
}
[NumericType(typeof(double), sizeof(double), 4, 1, "global::Misaki.HighPerformance.Mathematics.double")]
public partial struct double4
{
}
[NumericType(typeof(double4), sizeof(double), 4, 2, "global::Misaki.HighPerformance.Mathematics.double", elementType: typeof(double))]
public partial struct double4x2
{
}
[NumericType(typeof(double4), sizeof(double), 4, 3, "global::Misaki.HighPerformance.Mathematics.double", elementType: typeof(double))]
public partial struct double4x3
{
}
[NumericType(typeof(double4), sizeof(double), 4, 4, "global::Misaki.HighPerformance.Mathematics.double", elementType: typeof(double))]
public partial struct double4x4
{
}

View File

@@ -0,0 +1,75 @@
using Misaki.HighPerformance.Mathematics.Attributes;
namespace Misaki.HighPerformance.Mathematics;
[NumericType(typeof(float), sizeof(float), 2, 1, "global::Misaki.HighPerformance.Mathematics.float")]
public partial struct float2
{
}
[NumericType(typeof(float2), sizeof(float), 2, 2, "global::Misaki.HighPerformance.Mathematics.float", elementType: typeof(float))]
public partial struct float2x2
{
}
[NumericType(typeof(float2), sizeof(float), 2, 3, "global::Misaki.HighPerformance.Mathematics.float", elementType: typeof(float))]
public partial struct float2x3
{
}
[NumericType(typeof(float2), sizeof(float), 2, 4, "global::Misaki.HighPerformance.Mathematics.float", elementType: typeof(float))]
public partial struct float2x4
{
}
[NumericType(typeof(float), sizeof(float), 3, 1, "global::Misaki.HighPerformance.Mathematics.float")]
public partial struct float3
{
}
[NumericType(typeof(float3), sizeof(float), 3, 2, "global::Misaki.HighPerformance.Mathematics.float", elementType: typeof(float))]
public partial struct float3x2
{
}
[NumericType(typeof(float3), sizeof(float), 3, 3, "global::Misaki.HighPerformance.Mathematics.float", elementType: typeof(float))]
public partial struct float3x3
{
public float3x3(quaternion q)
{
var v = q.value;
var v2 = v + v;
var npn = new uint3(0x80000000, 0x00000000, 0x80000000);
var nnp = new uint3(0x80000000, 0x80000000, 0x00000000);
var pnn = new uint3(0x00000000, 0x80000000, 0x80000000);
c0 = v2.y * math.asfloat(math.asuint(v.yxw) ^ npn) - v2.z * math.asfloat(math.asuint(v.zwx) ^ pnn) + new float3(1, 0, 0);
c1 = v2.z * math.asfloat(math.asuint(v.wzy) ^ nnp) - v2.x * math.asfloat(math.asuint(v.yxw) ^ npn) + new float3(0, 1, 0);
c2 = v2.x * math.asfloat(math.asuint(v.zwx) ^ pnn) - v2.y * math.asfloat(math.asuint(v.wzy) ^ nnp) + new float3(0, 0, 1);
}
}
[NumericType(typeof(float3), sizeof(float), 3, 4, "global::Misaki.HighPerformance.Mathematics.float", elementType: typeof(float))]
public partial struct float3x4
{
}
[NumericType(typeof(float), sizeof(float), 4, 1, "global::Misaki.HighPerformance.Mathematics.float")]
public partial struct float4
{
}
[NumericType(typeof(float4), sizeof(float), 4, 2, "global::Misaki.HighPerformance.Mathematics.float", elementType: typeof(float))]
public partial struct float4x2
{
}
[NumericType(typeof(float4), sizeof(float), 4, 3, "global::Misaki.HighPerformance.Mathematics.float", elementType: typeof(float))]
public partial struct float4x3
{
}
[NumericType(typeof(float4), sizeof(float), 4, 4, "global::Misaki.HighPerformance.Mathematics.float", elementType: typeof(float))]
public partial struct float4x4
{
}

View File

@@ -1,272 +0,0 @@
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
public struct float2
{
public float x;
public float y;
public float2(float value)
{
this.x = value;
this.y = value;
}
public float2(float x, float y)
{
this.x = x;
this.y = y;
}
public float2(float3 value)
{
this.x = value.x;
this.y = value.y;
}
public float2(float4 value)
{
this.x = value.x;
this.y = value.y;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator +(float2 lhs, float2 rhs)
{
return (lhs.AsVector128() + rhs.AsVector128()).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator +(float2 lhs, float rhs)
{
return lhs + new float2(rhs);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator -(float2 lhs, float2 rhs)
{
return (lhs.AsVector128() - rhs.AsVector128()).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator -(float2 lhs, float rhs)
{
return lhs - new float2(rhs);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator *(float2 lhs, float2 rhs)
{
return (lhs.AsVector128() * rhs.AsVector128()).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator *(float2 lhs, float rhs)
{
return (lhs.AsVector128() * rhs).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator /(float2 lhs, float2 rhs)
{
return (lhs.AsVector128() / rhs.AsVector128()).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator /(float2 lhs, float rhs)
{
return (lhs.AsVector128() / rhs).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator -(float2 value)
{
return (-value.AsVector128()).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(float2 lhs, float2 rhs)
{
return lhs.AsVector128() == rhs.AsVector128();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(float2 lhs, float2 rhs)
{
return !(lhs == rhs);
}
public readonly float2 xx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.x, this.x);
}
public readonly float2 xy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.x, this.y);
}
public readonly float2 yx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.y, this.x);
}
public readonly float2 yy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.y, this.y);
}
public readonly float3 xxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.x, this.x);
}
public readonly float3 xxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.x, this.y);
}
public readonly float3 xyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.y, this.x);
}
public readonly float3 xyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.y, this.y);
}
public readonly float3 yxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.x, this.x);
}
public readonly float3 yxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.x, this.y);
}
public readonly float3 yyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.y, this.x);
}
public readonly float3 yyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.y, this.y);
}
public readonly float4 xxxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.x, this.x);
}
public readonly float4 xxxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.x, this.y);
}
public readonly float4 xxyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.y, this.x);
}
public readonly float4 xxyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.y, this.y);
}
public readonly float4 xyxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.x, this.x);
}
public readonly float4 xyxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.x, this.y);
}
public readonly float4 xyyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.y, this.x);
}
public readonly float4 xyyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.y, this.y);
}
public readonly float4 yxxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.x, this.x);
}
public readonly float4 yxxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.x, this.y);
}
public readonly float4 yxyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.y, this.x);
}
public readonly float4 yxyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.y, this.y);
}
public readonly float4 yyxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.x, this.x);
}
public readonly float4 yyxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.x, this.y);
}
public readonly float4 yyyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.y, this.x);
}
public readonly float4 yyyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.y, this.y);
}
public override readonly string ToString()
{
return $"(x: {this.x}, y: {this.y})";
}
}

View File

@@ -1,161 +0,0 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ include file="Utilities.ttinclude" #>
<#@ output extension=".gen.cs" #>
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
<#
var dimension = 2;
var components = new[] { "x", "y" };
var structName = $"float{dimension}";
#>
public struct <#= structName #>
{
<# for(int i = 0; i < dimension; i++) { #>
public float <#= components[i] #>;
<# } #>
public <#= structName #>(float value)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = value;
<# } #>
}
public <#= structName #>(<#= string.Join(", ", components.Take(dimension).Select(c => $"float {c}")) #>)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = <#= components[i] #>;
<# } #>
}
<# foreach (var otherDim in dimensions.Where(d => d != dimension))
{
string otherStructName = $"float{otherDim}";
if (otherDim < dimension)
{
#>
public <#= structName #>(<#= otherStructName #> value)
{
<# for(int i = 0; i < Math.Min(dimension, otherDim); i++) { #>
this.<#= components[i] #> = value.<#= components[i] #>;
<#
}
for(int i = otherDim; i < dimension; i++) {
#>
this.<#= components[i] #> = 0.0f;
<# } #>
}
<# }
else
{ #>
public <#= structName #>(<#= otherStructName #> value)
{
<#
for(int i = 0; i < dimension; i++) {
#>
this.<#= components[i] #> = value.<#= components[i] #>;
<# } #>
}
<#
}
}
#>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator +(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() + rhs.AsVector128()).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator +(<#= structName #> lhs, float rhs)
{
return lhs + new <#= structName #>(rhs);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator -(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() - rhs.AsVector128()).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator -(<#= structName #> lhs, float rhs)
{
return lhs - new <#= structName #>(rhs);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator *(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() * rhs.AsVector128()).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator *(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() * rhs).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator /(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() / rhs.AsVector128()).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator /(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() / rhs).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator -(<#= structName #> value)
{
return (-value.AsVector128()).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(<#= structName #> lhs, <#= structName #> rhs)
{
return lhs.AsVector128() == rhs.AsVector128();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(<#= structName #> lhs, <#= structName #> rhs)
{
return !(lhs == rhs);
}
<#
var validComponents = components.Take(dimension).ToArray();
var swizzles = GenerateSwizzles(validComponents, 4);
foreach (var swizzle in swizzles)
{
var targetDim = swizzle.Length;
var targetStruct = $"float{targetDim}";
#>
public readonly <#= targetStruct #> <#= swizzle #>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new <#= targetStruct #>(<#= string.Join(", ", swizzle.Select(c => $"this.{c}")) #>);
}
<#
}
#>
public override readonly string ToString()
{
return $"(<#= string.Join(", ", components.Take(dimension).Select(c => $"{c}: {{this.{c}}}")) #>)";
}
}

View File

@@ -1,791 +0,0 @@
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
public struct float3
{
public float x;
public float y;
public float z;
public float3(float value)
{
this.x = value;
this.y = value;
this.z = value;
}
public float3(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
public float3(float2 value)
{
this.x = value.x;
this.y = value.y;
this.z = 0.0f;
}
public float3(float4 value)
{
this.x = value.x;
this.y = value.y;
this.z = value.z;
}
public static float3 operator +(float3 lhs, float3 rhs)
{
return (lhs.AsVector128() + rhs.AsVector128()).AsFloat3();
}
public static float3 operator +(float3 lhs, float rhs)
{
return lhs + new float3(rhs);
}
public static float3 operator -(float3 lhs, float3 rhs)
{
return (lhs.AsVector128() - rhs.AsVector128()).AsFloat3();
}
public static float3 operator -(float3 lhs, float rhs)
{
return lhs - new float3(rhs);
}
public static float3 operator *(float3 lhs, float3 rhs)
{
return (lhs.AsVector128() * rhs.AsVector128()).AsFloat3();
}
public static float3 operator *(float3 lhs, float rhs)
{
return (lhs.AsVector128() * rhs).AsFloat3();
}
public static float3 operator /(float3 lhs, float3 rhs)
{
return (lhs.AsVector128() / rhs.AsVector128()).AsFloat3();
}
public static float3 operator /(float3 lhs, float rhs)
{
return (lhs.AsVector128() / rhs).AsFloat3();
}
public static float3 operator -(float3 value)
{
return (-value.AsVector128()).AsFloat3();
}
public float2 xx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.x, this.x);
}
public float2 xy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.x, this.y);
}
public float2 xz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.x, this.z);
}
public float2 yx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.y, this.x);
}
public float2 yy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.y, this.y);
}
public float2 yz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.y, this.z);
}
public float2 zx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.z, this.x);
}
public float2 zy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.z, this.y);
}
public float2 zz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.z, this.z);
}
public float3 xxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.x, this.x);
}
public float3 xxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.x, this.y);
}
public float3 xxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.x, this.z);
}
public float3 xyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.y, this.x);
}
public float3 xyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.y, this.y);
}
public float3 xyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.y, this.z);
}
public float3 xzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.z, this.x);
}
public float3 xzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.z, this.y);
}
public float3 xzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.z, this.z);
}
public float3 yxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.x, this.x);
}
public float3 yxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.x, this.y);
}
public float3 yxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.x, this.z);
}
public float3 yyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.y, this.x);
}
public float3 yyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.y, this.y);
}
public float3 yyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.y, this.z);
}
public float3 yzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.z, this.x);
}
public float3 yzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.z, this.y);
}
public float3 yzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.z, this.z);
}
public float3 zxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.x, this.x);
}
public float3 zxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.x, this.y);
}
public float3 zxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.x, this.z);
}
public float3 zyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.y, this.x);
}
public float3 zyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.y, this.y);
}
public float3 zyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.y, this.z);
}
public float3 zzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.z, this.x);
}
public float3 zzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.z, this.y);
}
public float3 zzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.z, this.z);
}
public float4 xxxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.x, this.x);
}
public float4 xxxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.x, this.y);
}
public float4 xxxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.x, this.z);
}
public float4 xxyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.y, this.x);
}
public float4 xxyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.y, this.y);
}
public float4 xxyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.y, this.z);
}
public float4 xxzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.z, this.x);
}
public float4 xxzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.z, this.y);
}
public float4 xxzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.z, this.z);
}
public float4 xyxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.x, this.x);
}
public float4 xyxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.x, this.y);
}
public float4 xyxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.x, this.z);
}
public float4 xyyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.y, this.x);
}
public float4 xyyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.y, this.y);
}
public float4 xyyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.y, this.z);
}
public float4 xyzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.z, this.x);
}
public float4 xyzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.z, this.y);
}
public float4 xyzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.z, this.z);
}
public float4 xzxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.x, this.x);
}
public float4 xzxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.x, this.y);
}
public float4 xzxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.x, this.z);
}
public float4 xzyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.y, this.x);
}
public float4 xzyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.y, this.y);
}
public float4 xzyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.y, this.z);
}
public float4 xzzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.z, this.x);
}
public float4 xzzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.z, this.y);
}
public float4 xzzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.z, this.z);
}
public float4 yxxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.x, this.x);
}
public float4 yxxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.x, this.y);
}
public float4 yxxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.x, this.z);
}
public float4 yxyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.y, this.x);
}
public float4 yxyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.y, this.y);
}
public float4 yxyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.y, this.z);
}
public float4 yxzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.z, this.x);
}
public float4 yxzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.z, this.y);
}
public float4 yxzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.z, this.z);
}
public float4 yyxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.x, this.x);
}
public float4 yyxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.x, this.y);
}
public float4 yyxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.x, this.z);
}
public float4 yyyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.y, this.x);
}
public float4 yyyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.y, this.y);
}
public float4 yyyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.y, this.z);
}
public float4 yyzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.z, this.x);
}
public float4 yyzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.z, this.y);
}
public float4 yyzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.z, this.z);
}
public float4 yzxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.x, this.x);
}
public float4 yzxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.x, this.y);
}
public float4 yzxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.x, this.z);
}
public float4 yzyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.y, this.x);
}
public float4 yzyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.y, this.y);
}
public float4 yzyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.y, this.z);
}
public float4 yzzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.z, this.x);
}
public float4 yzzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.z, this.y);
}
public float4 yzzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.z, this.z);
}
public float4 zxxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.x, this.x);
}
public float4 zxxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.x, this.y);
}
public float4 zxxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.x, this.z);
}
public float4 zxyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.y, this.x);
}
public float4 zxyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.y, this.y);
}
public float4 zxyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.y, this.z);
}
public float4 zxzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.z, this.x);
}
public float4 zxzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.z, this.y);
}
public float4 zxzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.z, this.z);
}
public float4 zyxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.x, this.x);
}
public float4 zyxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.x, this.y);
}
public float4 zyxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.x, this.z);
}
public float4 zyyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.y, this.x);
}
public float4 zyyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.y, this.y);
}
public float4 zyyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.y, this.z);
}
public float4 zyzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.z, this.x);
}
public float4 zyzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.z, this.y);
}
public float4 zyzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.z, this.z);
}
public float4 zzxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.x, this.x);
}
public float4 zzxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.x, this.y);
}
public float4 zzxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.x, this.z);
}
public float4 zzyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.y, this.x);
}
public float4 zzyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.y, this.y);
}
public float4 zzyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.y, this.z);
}
public float4 zzzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.z, this.x);
}
public float4 zzzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.z, this.y);
}
public float4 zzzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.z, this.z);
}
public override readonly string ToString()
{
return $"(x: {this.x}, y: {this.y}, z: {this.z})";
}
}

View File

@@ -1,140 +0,0 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ include file="Utilities.ttinclude" #>
<#@ output extension=".gen.cs" #>
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
<#
var dimension = 3;
var components = new[] { "x", "y", "z" };
var structName = $"float{dimension}";
#>
public struct <#= structName #>
{
<# for(int i = 0; i < dimension; i++) { #>
public float <#= components[i] #>;
<# } #>
public <#= structName #>(float value)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = value;
<# } #>
}
public <#= structName #>(<#= string.Join(", ", components.Take(dimension).Select(c => $"float {c}")) #>)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = <#= components[i] #>;
<# } #>
}
<# foreach (var otherDim in dimensions.Where(d => d != dimension))
{
string otherStructName = $"float{otherDim}";
if (otherDim < dimension)
{
#>
public <#= structName #>(<#= otherStructName #> value)
{
<# for(int i = 0; i < Math.Min(dimension, otherDim); i++) { #>
this.<#= components[i] #> = value.<#= components[i] #>;
<#
}
// Fill remaining components with 0
for(int i = otherDim; i < dimension; i++) {
#>
this.<#= components[i] #> = 0.0f;
<# } #>
}
<# }
else
{ #>
public <#= structName #>(<#= otherStructName #> value)
{
<#
for(int i = 0; i < dimension; i++) {
#>
this.<#= components[i] #> = value.<#= components[i] #>;
<# } #>
}
<#
}
}
#>
public static <#= structName #> operator +(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() + rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator +(<#= structName #> lhs, float rhs)
{
return lhs + new <#= structName #>(rhs);
}
public static <#= structName #> operator -(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() - rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator -(<#= structName #> lhs, float rhs)
{
return lhs - new <#= structName #>(rhs);
}
public static <#= structName #> operator *(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() * rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator *(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() * rhs).AsFloat<#= dimension #>();
}
public static <#= structName #> operator /(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() / rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator /(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() / rhs).AsFloat<#= dimension #>();
}
public static <#= structName #> operator -(<#= structName #> value)
{
return (-value.AsVector128()).AsFloat<#= dimension #>();
}
<#
var validComponents = components.Take(dimension).ToArray();
var swizzles = GenerateSwizzles(validComponents, 4);
foreach (var swizzle in swizzles)
{
var targetDim = swizzle.Length;
var targetStruct = $"float{targetDim}";
#>
public <#= targetStruct #> <#= swizzle #>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new <#= targetStruct #>(<#= string.Join(", ", swizzle.Select(c => $"this.{c}")) #>);
}
<#
}
#>
public override readonly string ToString()
{
return $"(<#= string.Join(", ", components.Take(dimension).Select(c => $"{c}: {{this.{c}}}")) #>)";
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,140 +0,0 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ include file="Utilities.ttinclude" #>
<#@ output extension=".gen.cs" #>
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
<#
var dimension = 4;
var components = new[] { "x", "y", "z", "w" };
var structName = $"float{dimension}";
#>
public struct <#= structName #>
{
<# for(int i = 0; i < dimension; i++) { #>
public float <#= components[i] #>;
<# } #>
public <#= structName #>(float value)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = value;
<# } #>
}
public <#= structName #>(<#= string.Join(", ", components.Take(dimension).Select(c => $"float {c}")) #>)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = <#= components[i] #>;
<# } #>
}
<# foreach (var otherDim in dimensions.Where(d => d != dimension))
{
string otherStructName = $"float{otherDim}";
if (otherDim < dimension)
{
#>
public <#= structName #>(<#= otherStructName #> value)
{
<# for(int i = 0; i < Math.Min(dimension, otherDim); i++) { #>
this.<#= components[i] #> = value.<#= components[i] #>;
<#
}
// Fill remaining components with 0
for(int i = otherDim; i < dimension; i++) {
#>
this.<#= components[i] #> = 0.0f;
<# } #>
}
<# }
else
{ #>
public <#= structName #>(<#= otherStructName #> value)
{
<#
for(int i = 0; i < dimension; i++) {
#>
this.<#= components[i] #> = value.<#= components[i] #>;
<# } #>
}
<#
}
}
#>
public static <#= structName #> operator +(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() + rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator +(<#= structName #> lhs, float rhs)
{
return lhs + new <#= structName #>(rhs);
}
public static <#= structName #> operator -(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() - rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator -(<#= structName #> lhs, float rhs)
{
return lhs - new <#= structName #>(rhs);
}
public static <#= structName #> operator *(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() * rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator *(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() * rhs).AsFloat<#= dimension #>();
}
public static <#= structName #> operator /(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() / rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator /(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() / rhs).AsFloat<#= dimension #>();
}
public static <#= structName #> operator -(<#= structName #> value)
{
return (-value.AsVector128()).AsFloat<#= dimension #>();
}
<#
var validComponents = components.Take(dimension).ToArray();
var swizzles = GenerateSwizzles(validComponents, 4);
foreach (var swizzle in swizzles)
{
var targetDim = swizzle.Length;
var targetStruct = $"float{targetDim}";
#>
public <#= targetStruct #> <#= swizzle #>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new <#= targetStruct #>(<#= string.Join(", ", swizzle.Select(c => $"this.{c}")) #>);
}
<#
}
#>
public override readonly string ToString()
{
return $"(<#= string.Join(", ", components.Take(dimension).Select(c => $"{c}: {{this.{c}}}")) #>)";
}
}

View File

@@ -0,0 +1,63 @@
using Misaki.HighPerformance.Mathematics.Attributes;
namespace Misaki.HighPerformance.Mathematics;
[NumericType(typeof(int), sizeof(int), 2, 1, "global::Misaki.HighPerformance.Mathematics.int")]
public partial struct int2
{
}
[NumericType(typeof(int2), sizeof(int), 2, 2, "global::Misaki.HighPerformance.Mathematics.int", canInverse: false, elementType: typeof(int))]
public partial struct int2x2
{
}
[NumericType(typeof(int2), sizeof(int), 2, 3, "global::Misaki.HighPerformance.Mathematics.int", canInverse: false, elementType: typeof(int))]
public partial struct int2x3
{
}
[NumericType(typeof(int2), sizeof(int), 2, 4, "global::Misaki.HighPerformance.Mathematics.int", canInverse: false, elementType: typeof(int))]
public partial struct int2x4
{
}
[NumericType(typeof(int), sizeof(int), 3, 1, "global::Misaki.HighPerformance.Mathematics.int")]
public partial struct int3
{
}
[NumericType(typeof(int3), sizeof(int), 3, 2, "global::Misaki.HighPerformance.Mathematics.int", canInverse: false, elementType: typeof(int))]
public partial struct int3x2
{
}
[NumericType(typeof(int3), sizeof(int), 3, 3, "global::Misaki.HighPerformance.Mathematics.int", canInverse: false, elementType: typeof(int))]
public partial struct int3x3
{
}
[NumericType(typeof(int3), sizeof(int), 3, 4, "global::Misaki.HighPerformance.Mathematics.int", canInverse: false, elementType: typeof(int))]
public partial struct int3x4
{
}
[NumericType(typeof(int), sizeof(int), 4, 1, "global::Misaki.HighPerformance.Mathematics.int")]
public partial struct int4
{
}
[NumericType(typeof(int4), sizeof(int), 4, 2, "global::Misaki.HighPerformance.Mathematics.int", canInverse: false, elementType: typeof(int))]
public partial struct int4x2
{
}
[NumericType(typeof(int4), sizeof(int), 4, 3, "global::Misaki.HighPerformance.Mathematics.int", canInverse: false, elementType: typeof(int))]
public partial struct int4x3
{
}
[NumericType(typeof(int4), sizeof(int), 4, 4, "global::Misaki.HighPerformance.Mathematics.int", canInverse: false, elementType: typeof(int))]
public partial struct int4x4
{
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,797 @@
using System.Runtime.CompilerServices;
using static Misaki.HighPerformance.Mathematics.math;
namespace Misaki.HighPerformance.Mathematics;
#pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
public partial struct quaternion : IEquatable<quaternion>
#pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
{
/// <summary>
/// The quaternion component values.
/// </summary>
public float4 value;
/// <summary>A quaternion representing the identity transform.</summary>
public static readonly quaternion identity = new quaternion(0.0f, 0.0f, 0.0f, 1.0f);
/// <summary>Constructs a quaternion from four float values.</summary>
/// <param name="x">The quaternion x component.</param>
/// <param name="y">The quaternion y component.</param>
/// <param name="z">The quaternion z component.</param>
/// <param name="w">The quaternion w component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public quaternion(float x, float y, float z, float w)
{
value.x = x;
value.y = y;
value.z = z;
value.w = w;
}
/// <summary>Constructs a quaternion from float4 vector.</summary>
/// <param name="value">The quaternion xyzw component values.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public quaternion(float4 value)
{
this.value = value;
}
/// <summary>Implicitly converts a float4 vector to a quaternion.</summary>
/// <param name="v">The quaternion xyzw component values.</param>
/// <returns>The quaternion constructed from a float4 vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator quaternion(float4 v)
{
return new quaternion(v);
}
/// <summary>Constructs a unit quaternion from a float3x3 rotation matrix. The matrix must be orthonormal.</summary>
/// <param name="m">The float3x3 orthonormal rotation matrix.</param>
public quaternion(float3x3 m)
{
var u = m.c0;
var v = m.c1;
var w = m.c2;
var u_sign = (asuint(u.x) & 0x80000000);
var t = v.y + asfloat(asuint(w.z) ^ u_sign);
var u_mask = new uint4(u_sign >> 31);
var t_mask = new uint4(asuint(t) >> 31);
var tr = 1.0f + abs(u.x);
var sign_flips = new uint4(0x00000000, 0x80000000, 0x80000000, 0x80000000) ^ (u_mask & new uint4(0x00000000, 0x80000000, 0x00000000, 0x80000000)) ^ (t_mask & new uint4(0x80000000, 0x80000000, 0x80000000, 0x00000000));
value = new float4(tr, u.y, w.x, v.z) + asfloat(asuint(new float4(t, v.x, u.z, w.y)) ^ sign_flips); // +---, +++-, ++-+, +-++
value = asfloat((asuint(value) & ~u_mask) | (asuint(value.zwxy) & u_mask));
value = asfloat((asuint(value.wzyx) & ~t_mask) | (asuint(value) & t_mask));
value = normalize(value);
}
/// <summary>Constructs a unit quaternion from an orthonormal float4x4 matrix.</summary>
/// <param name="m">The float4x4 orthonormal rotation matrix.</param>
public quaternion(float4x4 m)
{
var u = m.c0;
var v = m.c1;
var w = m.c2;
var u_sign = (asuint(u.x) & 0x80000000);
var t = v.y + asfloat(asuint(w.z) ^ u_sign);
var u_mask = new uint4(u_sign >> 31);
var t_mask = new uint4(asuint(t) >> 31);
var tr = 1.0f + abs(u.x);
var sign_flips = new uint4(0x00000000, 0x80000000, 0x80000000, 0x80000000) ^ (u_mask & new uint4(0x00000000, 0x80000000, 0x00000000, 0x80000000)) ^ (t_mask & new uint4(0x80000000, 0x80000000, 0x80000000, 0x00000000));
value = new float4(tr, u.y, w.x, v.z) + asfloat(asuint(new float4(t, v.x, u.z, w.y)) ^ sign_flips); // +---, +++-, ++-+, +-++
value = asfloat((asuint(value) & ~u_mask) | (asuint(value.zwxy) & u_mask));
value = asfloat((asuint(value.wzyx) & ~t_mask) | (asuint(value) & t_mask));
value = normalize(value);
}
/// <summary>
/// Returns a quaternion representing a rotation around a unit axis by an angle in radians.
/// The rotation direction is clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="axis">The axis of rotation.</param>
/// <param name="angle">The angle of rotation in radians.</param>
/// <returns>The quaternion representing a rotation around an axis.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion AxisAngle(float3 axis, float angle)
{
sincos(0.5f * angle, out var sina, out var cosa);
return new quaternion(new float4(axis * sina, cosa));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the x-axis, then the y-axis and finally the z-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="xyz">A float3 vector containing the rotation angles around the x-, y- and z-axis measures in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in x-y-z order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerXYZ(float3 xyz)
{
// return mul(rotateZ(xyz.z), mul(rotateY(xyz.y), rotateX(xyz.x)));
float3 s, c;
sincos(0.5f * xyz, out s, out c);
return new quaternion(
// s.x * c.y * c.z - s.y * s.z * c.x,
// s.y * c.x * c.z + s.x * s.z * c.y,
// s.z * c.x * c.y - s.x * s.y * c.z,
// c.x * c.y * c.z + s.y * s.z * s.x
new float4(s.xyz, c.x) * c.yxxy * c.zzyz + s.yxxy * s.zzyz * new float4(c.xyz, s.x) * new float4(-1.0f, 1.0f, -1.0f, 1.0f));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the x-axis, then the z-axis and finally the y-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="xyz">A float3 vector containing the rotation angles around the x-, y- and z-axis measures in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in x-z-y order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerXZY(float3 xyz)
{
// return mul(rotateY(xyz.y), mul(rotateZ(xyz.z), rotateX(xyz.x)));
float3 s, c;
sincos(0.5f * xyz, out s, out c);
return new quaternion(
// s.x * c.y * c.z + s.y * s.z * c.x,
// s.y * c.x * c.z + s.x * s.z * c.y,
// s.z * c.x * c.y - s.x * s.y * c.z,
// c.x * c.y * c.z - s.y * s.z * s.x
new float4(s.xyz, c.x) * c.yxxy * c.zzyz + s.yxxy * s.zzyz * new float4(c.xyz, s.x) * new float4(1.0f, 1.0f, -1.0f, -1.0f));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the y-axis, then the x-axis and finally the z-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="xyz">A float3 vector containing the rotation angles around the x-, y- and z-axis measures in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in y-x-z order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerYXZ(float3 xyz)
{
// return mul(rotateZ(xyz.z), mul(rotateX(xyz.x), rotateY(xyz.y)));
float3 s, c;
sincos(0.5f * xyz, out s, out c);
return new quaternion(
// s.x * c.y * c.z - s.y * s.z * c.x,
// s.y * c.x * c.z + s.x * s.z * c.y,
// s.z * c.x * c.y + s.x * s.y * c.z,
// c.x * c.y * c.z - s.y * s.z * s.x
new float4(s.xyz, c.x) * c.yxxy * c.zzyz + s.yxxy * s.zzyz * new float4(c.xyz, s.x) * new float4(-1.0f, 1.0f, 1.0f, -1.0f));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the y-axis, then the z-axis and finally the x-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="xyz">A float3 vector containing the rotation angles around the x-, y- and z-axis measures in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in y-z-x order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerYZX(float3 xyz)
{
// return mul(rotateX(xyz.x), mul(rotateZ(xyz.z), rotateY(xyz.y)));
float3 s, c;
sincos(0.5f * xyz, out s, out c);
return new quaternion(
// s.x * c.y * c.z - s.y * s.z * c.x,
// s.y * c.x * c.z - s.x * s.z * c.y,
// s.z * c.x * c.y + s.x * s.y * c.z,
// c.x * c.y * c.z + s.y * s.z * s.x
new float4(s.xyz, c.x) * c.yxxy * c.zzyz + s.yxxy * s.zzyz * new float4(c.xyz, s.x) * new float4(-1.0f, -1.0f, 1.0f, 1.0f));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the z-axis, then the x-axis and finally the y-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// This is the default order rotation order in Unity.
/// </summary>
/// <param name="xyz">A float3 vector containing the rotation angles around the x-, y- and z-axis measures in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in z-x-y order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerZXY(float3 xyz)
{
// return mul(rotateY(xyz.y), mul(rotateX(xyz.x), rotateZ(xyz.z)));
float3 s, c;
sincos(0.5f * xyz, out s, out c);
return new quaternion(
// s.x * c.y * c.z + s.y * s.z * c.x,
// s.y * c.x * c.z - s.x * s.z * c.y,
// s.z * c.x * c.y - s.x * s.y * c.z,
// c.x * c.y * c.z + s.y * s.z * s.x
new float4(s.xyz, c.x) * c.yxxy * c.zzyz + s.yxxy * s.zzyz * new float4(c.xyz, s.x) * new float4(1.0f, -1.0f, -1.0f, 1.0f));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the z-axis, then the y-axis and finally the x-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="xyz">A float3 vector containing the rotation angles around the x-, y- and z-axis measures in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in z-y-x order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerZYX(float3 xyz)
{
// return mul(rotateX(xyz.x), mul(rotateY(xyz.y), rotateZ(xyz.z)));
float3 s, c;
sincos(0.5f * xyz, out s, out c);
return new quaternion(
// s.x * c.y * c.z + s.y * s.z * c.x,
// s.y * c.x * c.z - s.x * s.z * c.y,
// s.z * c.x * c.y + s.x * s.y * c.z,
// c.x * c.y * c.z - s.y * s.x * s.z
new float4(s.xyz, c.x) * c.yxxy * c.zzyz + s.yxxy * s.zzyz * new float4(c.xyz, s.x) * new float4(1.0f, -1.0f, 1.0f, -1.0f));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the x-axis, then the y-axis and finally the z-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="x">The rotation angle around the x-axis in radians.</param>
/// <param name="y">The rotation angle around the y-axis in radians.</param>
/// <param name="z">The rotation angle around the z-axis in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in x-y-z order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerXYZ(float x, float y, float z)
{
return EulerXYZ(new float3(x, y, z));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the x-axis, then the z-axis and finally the y-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="x">The rotation angle around the x-axis in radians.</param>
/// <param name="y">The rotation angle around the y-axis in radians.</param>
/// <param name="z">The rotation angle around the z-axis in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in x-z-y order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerXZY(float x, float y, float z)
{
return EulerXZY(new float3(x, y, z));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the y-axis, then the x-axis and finally the z-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="x">The rotation angle around the x-axis in radians.</param>
/// <param name="y">The rotation angle around the y-axis in radians.</param>
/// <param name="z">The rotation angle around the z-axis in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in y-x-z order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerYXZ(float x, float y, float z)
{
return EulerYXZ(new float3(x, y, z));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the y-axis, then the z-axis and finally the x-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="x">The rotation angle around the x-axis in radians.</param>
/// <param name="y">The rotation angle around the y-axis in radians.</param>
/// <param name="z">The rotation angle around the z-axis in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in y-z-x order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerYZX(float x, float y, float z)
{
return EulerYZX(new float3(x, y, z));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the z-axis, then the x-axis and finally the y-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// This is the default order rotation order in Unity.
/// </summary>
/// <param name="x">The rotation angle around the x-axis in radians.</param>
/// <param name="y">The rotation angle around the y-axis in radians.</param>
/// <param name="z">The rotation angle around the z-axis in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in z-x-y order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerZXY(float x, float y, float z)
{
return EulerZXY(new float3(x, y, z));
}
/// <summary>
/// Returns a quaternion constructed by first performing a rotation around the z-axis, then the y-axis and finally the x-axis.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// </summary>
/// <param name="x">The rotation angle around the x-axis in radians.</param>
/// <param name="y">The rotation angle around the y-axis in radians.</param>
/// <param name="z">The rotation angle around the z-axis in radians.</param>
/// <returns>The quaternion representing the Euler angle rotation in z-y-x order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion EulerZYX(float x, float y, float z)
{
return EulerZYX(new float3(x, y, z));
}
/// <summary>
/// Returns a quaternion constructed by first performing 3 rotations around the principal axes in a given order.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// When the rotation order is known at compile time, it is recommended for performance reasons to use specific
/// Euler rotation constructors such as EulerZXY(...).
/// </summary>
/// <param name="xyz">A float3 vector containing the rotation angles around the x-, y- and z-axis measures in radians.</param>
/// <param name="order">The order in which the rotations are applied.</param>
/// <returns>The quaternion representing the Euler angle rotation in the specified order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion Euler(float3 xyz, RotationOrder order = RotationOrder.ZXY)
{
switch (order)
{
case RotationOrder.XYZ:
return EulerXYZ(xyz);
case RotationOrder.XZY:
return EulerXZY(xyz);
case RotationOrder.YXZ:
return EulerYXZ(xyz);
case RotationOrder.YZX:
return EulerYZX(xyz);
case RotationOrder.ZXY:
return EulerZXY(xyz);
case RotationOrder.ZYX:
return EulerZYX(xyz);
default:
return identity;
}
}
/// <summary>
/// Returns a quaternion constructed by first performing 3 rotations around the principal axes in a given order.
/// All rotation angles are in radians and clockwise when looking along the rotation axis towards the origin.
/// When the rotation order is known at compile time, it is recommended for performance reasons to use specific
/// Euler rotation constructors such as EulerZXY(...).
/// </summary>
/// <param name="x">The rotation angle around the x-axis in radians.</param>
/// <param name="y">The rotation angle around the y-axis in radians.</param>
/// <param name="z">The rotation angle around the z-axis in radians.</param>
/// <param name="order">The order in which the rotations are applied.</param>
/// <returns>The quaternion representing the Euler angle rotation in the specified order.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion Euler(float x, float y, float z, RotationOrder order = RotationOrder.Default)
{
return Euler(new float3(x, y, z), order);
}
/// <summary>Returns a quaternion that rotates around the x-axis by a given number of radians.</summary>
/// <param name="angle">The clockwise rotation angle when looking along the x-axis towards the origin in radians.</param>
/// <returns>The quaternion representing a rotation around the x-axis.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion RotateX(float angle)
{
float sina, cosa;
sincos(0.5f * angle, out sina, out cosa);
return new quaternion(sina, 0.0f, 0.0f, cosa);
}
/// <summary>Returns a quaternion that rotates around the y-axis by a given number of radians.</summary>
/// <param name="angle">The clockwise rotation angle when looking along the y-axis towards the origin in radians.</param>
/// <returns>The quaternion representing a rotation around the y-axis.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion RotateY(float angle)
{
float sina, cosa;
sincos(0.5f * angle, out sina, out cosa);
return new quaternion(0.0f, sina, 0.0f, cosa);
}
/// <summary>Returns a quaternion that rotates around the z-axis by a given number of radians.</summary>
/// <param name="angle">The clockwise rotation angle when looking along the z-axis towards the origin in radians.</param>
/// <returns>The quaternion representing a rotation around the z-axis.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion RotateZ(float angle)
{
float sina, cosa;
sincos(0.5f * angle, out sina, out cosa);
return new quaternion(0.0f, 0.0f, sina, cosa);
}
/// <summary>
/// Returns a quaternion view rotation given a unit length forward vector and a unit length up vector.
/// The two input vectors are assumed to be unit length and not collinear.
/// If these assumptions are not met use float3x3.LookRotationSafe instead.
/// </summary>
/// <param name="forward">The view forward direction.</param>
/// <param name="up">The view up direction.</param>
/// <returns>The quaternion view rotation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion LookRotation(float3 forward, float3 up)
{
var t = normalize(cross(up, forward));
return new quaternion(new float3x3(t, cross(forward, t), forward));
}
/// <summary>
/// Returns a quaternion view rotation given a forward vector and an up vector.
/// The two input vectors are not assumed to be unit length.
/// If the magnitude of either of the vectors is so extreme that the calculation cannot be carried out reliably or the vectors are collinear,
/// the identity will be returned instead.
/// </summary>
/// <param name="forward">The view forward direction.</param>
/// <param name="up">The view up direction.</param>
/// <returns>The quaternion view rotation or the identity quaternion.</returns>
public static quaternion LookRotationSafe(float3 forward, float3 up)
{
var forwardLengthSq = dot(forward, forward);
var upLengthSq = dot(up, up);
forward *= rsqrt(forwardLengthSq);
up *= rsqrt(upLengthSq);
var t = cross(up, forward);
var tLengthSq = dot(t, t);
t *= rsqrt(tLengthSq);
var mn = min(min(forwardLengthSq, upLengthSq), tLengthSq);
var mx = max(max(forwardLengthSq, upLengthSq), tLengthSq);
var accept = mn > 1e-35f && mx < 1e35f && isfinite(forwardLengthSq) && isfinite(upLengthSq) && isfinite(tLengthSq);
return new quaternion(select(float4.unitW, new quaternion(new float3x3(t, cross(forward, t), forward)).value, accept));
}
/// <summary>Returns true if the quaternion is equal to a given quaternion, false otherwise.</summary>
/// <param name="x">The quaternion to compare with.</param>
/// <returns>True if the quaternion is equal to the input, false otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(quaternion x)
{
return value.x == x.value.x && value.y == x.value.y && value.z == x.value.z && value.w == x.value.w;
}
/// <summary>Returns whether true if the quaternion is equal to a given quaternion, false otherwise.</summary>
/// <param name="x">The object to compare with.</param>
/// <returns>True if the quaternion is equal to the input, false otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object? x)
{
return x is quaternion converted && Equals(converted);
}
/// <summary>Returns a hash code for the quaternion.</summary>
/// <returns>The hash code of the quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
{
return (int)hash(this);
}
/// <summary>Returns a string representation of the quaternion.</summary>
/// <returns>The string representation of the quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override string ToString()
{
return string.Format("quaternion({0}f, {1}f, {2}f, {3}f)", value.x, value.y, value.z, value.w);
}
}
#pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
public static partial class math
#pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
{
/// <summary>Returns a quaternion constructed from four float values.</summary>
/// <param name="x">The x component of the quaternion.</param>
/// <param name="y">The y component of the quaternion.</param>
/// <param name="z">The z component of the quaternion.</param>
/// <param name="w">The w component of the quaternion.</param>
/// <returns>The quaternion constructed from individual components.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion quaternion(float x, float y, float z, float w)
{
return new quaternion(x, y, z, w);
}
/// <summary>Returns a quaternion constructed from a float4 vector.</summary>
/// <param name="value">The float4 containing the components of the quaternion.</param>
/// <returns>The quaternion constructed from a float4.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion quaternion(float4 value)
{
return new quaternion(value);
}
/// <summary>Returns the conjugate of a quaternion value.</summary>
/// <param name="q">The quaternion to conjugate.</param>
/// <returns>The conjugate of the input quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion conjugate(quaternion q)
{
return new quaternion(q.value * new float4(-1.0f, -1.0f, -1.0f, 1.0f));
}
/// <summary>Returns the inverse of a quaternion value.</summary>
/// <param name="q">The quaternion to invert.</param>
/// <returns>The quaternion inverse of the input quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion inverse(quaternion q)
{
var x = q.value;
return quaternion(rcp(dot(x, x)) * x * new float4(-1.0f, -1.0f, -1.0f, 1.0f));
}
/// <summary>Returns the dot product of two quaternions.</summary>
/// <param name="a">The first quaternion.</param>
/// <param name="b">The second quaternion.</param>
/// <returns>The dot product of two quaternions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float dot(quaternion a, quaternion b)
{
return dot(a.value, b.value);
}
/// <summary>Returns the length of a quaternion.</summary>
/// <param name="q">The input quaternion.</param>
/// <returns>The length of the input quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float length(quaternion q)
{
return sqrt(dot(q.value, q.value));
}
/// <summary>Returns the squared length of a quaternion.</summary>
/// <param name="q">The input quaternion.</param>
/// <returns>The length squared of the input quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float lengthsq(quaternion q)
{
return dot(q.value, q.value);
}
/// <summary>Returns a normalized version of a quaternion q by scaling it by 1 / length(q).</summary>
/// <param name="q">The quaternion to normalize.</param>
/// <returns>The normalized quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion normalize(quaternion q)
{
var x = q.value;
return quaternion(rsqrt(dot(x, x)) * x);
}
/// <summary>
/// Returns a safe normalized version of the q by scaling it by 1 / length(q).
/// Returns the identity when 1 / length(q) does not produce a finite number.
/// </summary>
/// <param name="q">The quaternion to normalize.</param>
/// <returns>The normalized quaternion or the identity quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion normalizesafe(quaternion q)
{
var x = q.value;
var len = dot(x, x);
return quaternion(select(Mathematics.quaternion.identity.value, x * rsqrt(len), len > FLT_MIN_NORMAL));
}
/// <summary>
/// Returns a safe normalized version of the q by scaling it by 1 / length(q).
/// Returns the given default value when 1 / length(q) does not produce a finite number.
/// </summary>
/// <param name="q">The quaternion to normalize.</param>
/// <param name="defaultvalue">The default value.</param>
/// <returns>The normalized quaternion or the default value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion normalizesafe(quaternion q, quaternion defaultvalue)
{
var x = q.value;
var len = dot(x, x);
return quaternion(select(defaultvalue.value, x * rsqrt(len), len > FLT_MIN_NORMAL));
}
/// <summary>Returns the natural exponent of a quaternion. Assumes w is zero.</summary>
/// <param name="q">The quaternion with w component equal to zero.</param>
/// <returns>The natural exponent of the input quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion unitexp(quaternion q)
{
var v_rcp_len = rsqrt(dot(q.value.xyz, q.value.xyz));
var v_len = rcp(v_rcp_len);
float sin_v_len, cos_v_len;
sincos(v_len, out sin_v_len, out cos_v_len);
return quaternion(new float4(q.value.xyz * v_rcp_len * sin_v_len, cos_v_len));
}
/// <summary>Returns the natural exponent of a quaternion.</summary>
/// <param name="q">The quaternion.</param>
/// <returns>The natural exponent of the input quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion exp(quaternion q)
{
var v_rcp_len = rsqrt(dot(q.value.xyz, q.value.xyz));
var v_len = rcp(v_rcp_len);
float sin_v_len, cos_v_len;
sincos(v_len, out sin_v_len, out cos_v_len);
return quaternion(new float4(q.value.xyz * v_rcp_len * sin_v_len, cos_v_len) * exp(q.value.w));
}
/// <summary>Returns the natural logarithm of a unit length quaternion.</summary>
/// <param name="q">The unit length quaternion.</param>
/// <returns>The natural logarithm of the unit length quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion unitlog(quaternion q)
{
var w = clamp(q.value.w, -1.0f, 1.0f);
var s = acos(w) * rsqrt(1.0f - w * w);
return quaternion(new float4(q.value.xyz * s, 0.0f));
}
/// <summary>Returns the natural logarithm of a quaternion.</summary>
/// <param name="q">The quaternion.</param>
/// <returns>The natural logarithm of the input quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion log(quaternion q)
{
var v_len_sq = dot(q.value.xyz, q.value.xyz);
var q_len_sq = v_len_sq + q.value.w * q.value.w;
var s = acos(clamp(q.value.w * rsqrt(q_len_sq), -1.0f, 1.0f)) * rsqrt(v_len_sq);
return quaternion(new float4(q.value.xyz * s, 0.5f * log(q_len_sq)));
}
/// <summary>Returns the result of transforming the quaternion b by the quaternion a.</summary>
/// <param name="a">The quaternion on the left.</param>
/// <param name="b">The quaternion on the right.</param>
/// <returns>The result of transforming quaternion b by the quaternion a.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion mul(quaternion a, quaternion b)
{
return quaternion(a.value.wwww * b.value + (a.value.xyzx * b.value.wwwx + a.value.yzxy * b.value.zxyy) * new float4(1.0f, 1.0f, 1.0f, -1.0f) - a.value.zxyz * b.value.yzxz);
}
/// <summary>Returns the result of transforming a vector by a quaternion.</summary>
/// <param name="q">The quaternion transformation.</param>
/// <param name="v">The vector to transform.</param>
/// <returns>The transformation of vector v by quaternion q.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float3 mul(quaternion q, float3 v)
{
var t = 2 * cross(q.value.xyz, v);
return v + q.value.w * t + cross(q.value.xyz, t);
}
/// <summary>Returns the result of rotating a vector by a unit quaternion.</summary>
/// <param name="q">The quaternion rotation.</param>
/// <param name="v">The vector to rotate.</param>
/// <returns>The rotation of vector v by quaternion q.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float3 rotate(quaternion q, float3 v)
{
var t = 2 * cross(q.value.xyz, v);
return v + q.value.w * t + cross(q.value.xyz, t);
}
/// <summary>Returns the result of a normalized linear interpolation between two quaternions q1 and a2 using an interpolation parameter t.</summary>
/// <remarks>
/// Prefer to use this over slerp() when you know the distance between q1 and q2 is small. This can be much
/// higher performance due to avoiding trigonometric function evaluations that occur in slerp().
/// </remarks>
/// <param name="q1">The first quaternion.</param>
/// <param name="q2">The second quaternion.</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The normalized linear interpolation of two quaternions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion nlerp(quaternion q1, quaternion q2, float t)
{
return normalize(q1.value + t * (chgsign(q2.value, dot(q1, q2)) - q1.value));
}
/// <summary>Returns the result of a spherical interpolation between two quaternions q1 and a2 using an interpolation parameter t.</summary>
/// <param name="q1">The first quaternion.</param>
/// <param name="q2">The second quaternion.</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The spherical linear interpolation of two quaternions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion slerp(quaternion q1, quaternion q2, float t)
{
var dt = dot(q1, q2);
if (dt < 0.0f)
{
dt = -dt;
q2.value = -q2.value;
}
if (dt < 0.9995f)
{
var angle = acos(dt);
var s = rsqrt(1.0f - dt * dt); // 1.0f / sin(angle)
var w1 = sin(angle * (1.0f - t)) * s;
var w2 = sin(angle * t) * s;
return quaternion(q1.value * w1 + q2.value * w2);
}
else
{
// if the angle is small, use linear interpolation
return nlerp(q1, q2, t);
}
}
/// <summary>Returns the angle in radians between two unit quaternions.</summary>
/// <param name="q1">The first quaternion.</param>
/// <param name="q2">The second quaternion.</param>
/// <returns>The angle between two unit quaternions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float angle(quaternion q1, quaternion q2)
{
var diff = asin(length(normalize(mul(conjugate(q1), q2)).value.xyz));
return diff + diff;
}
/// <summary>
/// Extracts the rotation from a matrix.
/// </summary>
/// <remarks>This method supports any type of rotation matrix: if the matrix has a non uniform scale you should use this method.</remarks>
/// <param name="m">Matrix to extract rotation from</param>
/// <returns>Extracted rotation</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion rotation(float3x3 m)
{
var det = determinant(m);
if (abs(1f - det) < svd.EPSILON_DETERMINANT)
{
return new quaternion(m);
}
if (abs(det) > svd.EPSILON_DETERMINANT)
{
var tmp = mulScale(m, rsqrt(new float3(lengthsq(m.c0), lengthsq(m.c1), lengthsq(m.c2))));
if (abs(1f - determinant(tmp)) < svd.EPSILON_DETERMINANT)
{
return new quaternion(tmp);
}
}
return svd.svdRotation(m);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static float3x3 adj(float3x3 m, out float det)
{
float3x3 adjT;
adjT.c0 = cross(m.c1, m.c2);
adjT.c1 = cross(m.c2, m.c0);
adjT.c2 = cross(m.c0, m.c1);
det = dot(m.c0, adjT.c0);
return transpose(adjT);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool adjInverse(float3x3 m, out float3x3 i, float epsilon = svd.EPSILON_NORMAL)
{
i = adj(m, out var det);
var c = abs(det) > epsilon;
var detInv = select(new float3(1f), rcp(det), c);
i = scaleMul(detInv, i);
return c;
}
/// <summary>Returns a uint hash code of a quaternion.</summary>
/// <param name="q">The quaternion to hash.</param>
/// <returns>The hash code for the input quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint hash(quaternion q)
{
return hash(q.value);
}
/// <summary>
/// Returns a uint4 vector hash code of a quaternion.
/// When multiple elements are to be hashes together, it can more efficient to calculate and combine wide hash
/// that are only reduced to a narrow uint hash at the very end instead of at every step.
/// </summary>
/// <param name="q">The quaternion to hash.</param>
/// <returns>The uint4 vector hash code of the input quaternion.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint4 hashwide(quaternion q)
{
return hashwide(q.value);
}
}

View File

@@ -0,0 +1,169 @@
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
// SVD algorithm as described in:
// Computing the singular value decomposition of 3x3 matrices with minimal branching and elementary floating point operations,
// A.McAdams, A.Selle, R.Tamstorf, J.Teran and E.Sifakis, University of Wisconsin - Madison technical report TR1690, May 2011
#pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
public static class svd
#pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
{
public const float EPSILON_DETERMINANT = 1e-6f;
public const float EPSILON_RCP = 1e-9f;
public const float EPSILON_NORMAL_SQRT = 1e-15f;
public const float EPSILON_NORMAL = 1e-30f;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void condSwap(bool c, ref float x, ref float y)
{
var tmp = x;
x = math.select(x, y, c);
y = math.select(y, tmp, c);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void condNegSwap(bool c, ref float3 x, ref float3 y)
{
var tmp = -x;
x = math.select(x, y, c);
y = math.select(y, tmp, c);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static quaternion condNegSwapQuat(bool c, quaternion q, float4 mask)
{
const float halfSqrt2 = 0.707106781186548f;
return math.mul(q, math.select(quaternion.identity.value, mask * halfSqrt2, c));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void sortSingularValues(ref float3x3 b, ref quaternion v)
{
var l0 = math.lengthsq(b.c0);
var l1 = math.lengthsq(b.c1);
var l2 = math.lengthsq(b.c2);
var c = l0 < l1;
condNegSwap(c, ref b.c0, ref b.c1);
v = condNegSwapQuat(c, v, new float4(0f, 0f, 1f, 1f));
condSwap(c, ref l0, ref l1);
c = l0 < l2;
condNegSwap(c, ref b.c0, ref b.c2);
v = condNegSwapQuat(c, v, new float4(0f, -1f, 0f, 1f));
condSwap(c, ref l0, ref l2);
c = l1 < l2;
condNegSwap(c, ref b.c1, ref b.c2);
v = condNegSwapQuat(c, v, new float4(1f, 0f, 0f, 1f));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static quaternion approxGivensQuat(float3 pq, float4 mask)
{
const float c8 = 0.923879532511287f; // cos(pi/8)
const float s8 = 0.38268343236509f; // sin(pi/8)
const float g = 5.82842712474619f; // 3 + 2 * sqrt(2)
var ch = 2f * (pq.x - pq.y); // approx cos(a/2)
var sh = pq.z; // approx sin(a/2)
var r = math.select(new float4(s8, s8, s8, c8), new float4(sh, sh, sh, ch), g * sh * sh < ch * ch) * mask;
return math.normalize(r);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static quaternion qrGivensQuat(float2 pq, float4 mask)
{
var l = math.sqrt(pq.x * pq.x + pq.y * pq.y);
var sh = math.select(0f, pq.y, l > EPSILON_NORMAL_SQRT);
var ch = math.abs(pq.x) + math.max(l, EPSILON_NORMAL_SQRT);
condSwap(pq.x < 0f, ref sh, ref ch);
return math.normalize(new float4(sh, sh, sh, ch) * mask);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static quaternion givensQRFactorization(float3x3 b, out float3x3 r)
{
var u = qrGivensQuat(new float2(b.c0.x, b.c0.y), new float4(0f, 0f, 1f, 1f));
var qmt = new float3x3(math.conjugate(u));
r = math.mul(qmt, b);
var q = qrGivensQuat(new float2(r.c0.x, r.c0.z), new float4(0f, -1f, 0f, 1f));
u = math.mul(u, q);
qmt = new float3x3(math.conjugate(q));
r = math.mul(qmt, r);
q = qrGivensQuat(new float2(r.c1.y, r.c1.z), new float4(1f, 0f, 0f, 1f));
u = math.mul(u, q);
qmt = new float3x3(math.conjugate(q));
r = math.mul(qmt, r);
return u;
}
static quaternion jacobiIteration(ref float3x3 s, int iterations = 5)
{
float3x3 qm;
quaternion q;
var v = quaternion.identity;
for (var i = 0; i < iterations; ++i)
{
q = approxGivensQuat(new float3(s.c0.x, s.c1.y, s.c0.y), new float4(0f, 0f, 1f, 1f));
v = math.mul(v, q);
qm = new float3x3(q);
s = math.mul(math.mul(math.transpose(qm), s), qm);
q = approxGivensQuat(new float3(s.c1.y, s.c2.z, s.c1.z), new float4(1f, 0f, 0f, 1f));
v = math.mul(v, q);
qm = new float3x3(q);
s = math.mul(math.mul(math.transpose(qm), s), qm);
q = approxGivensQuat(new float3(s.c2.z, s.c0.x, s.c2.x), new float4(0f, 1f, 0f, 1f));
v = math.mul(v, q);
qm = new float3x3(q);
s = math.mul(math.mul(math.transpose(qm), s), qm);
}
return v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static float3 singularValuesDecomposition(float3x3 a, out quaternion u, out quaternion v)
{
u = quaternion.identity;
v = quaternion.identity;
var s = math.mul(math.transpose(a), a);
v = jacobiIteration(ref s);
var b = new float3x3(v);
b = math.mul(a, b);
sortSingularValues(ref b, ref v);
u = givensQRFactorization(b, out var e);
return new float3(e.c0.x, e.c1.y, e.c2.z);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static float3 rcpsafe(float3 x, float epsilon = EPSILON_RCP) =>
math.select(math.rcp(x), float3.zero, math.abs(x) < epsilon);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float3x3 svdInverse(float3x3 a)
{
var e = singularValuesDecomposition(a, out var u, out var v);
var um = new float3x3(u);
var vm = new float3x3(v);
return math.mul(vm, math.scaleMul(rcpsafe(e, EPSILON_DETERMINANT), math.transpose(um)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion svdRotation(float3x3 a)
{
singularValuesDecomposition(a, out var u, out var v);
return math.mul(u, math.conjugate(v));
}
}

View File

@@ -0,0 +1,63 @@
using Misaki.HighPerformance.Mathematics.Attributes;
namespace Misaki.HighPerformance.Mathematics;
[NumericType(typeof(uint), sizeof(uint), 2, 1, "global::Misaki.HighPerformance.Mathematics.uint")]
public partial struct uint2
{
}
[NumericType(typeof(uint2), sizeof(uint), 2, 2, "global::Misaki.HighPerformance.Mathematics.uint", canInverse: false, elementType: typeof(uint))]
public partial struct uint2x2
{
}
[NumericType(typeof(uint2), sizeof(uint), 2, 3, "global::Misaki.HighPerformance.Mathematics.uint", canInverse: false, elementType: typeof(uint))]
public partial struct uint2x3
{
}
[NumericType(typeof(uint2), sizeof(uint), 2, 4, "global::Misaki.HighPerformance.Mathematics.uint", canInverse: false, elementType: typeof(uint))]
public partial struct uint2x4
{
}
[NumericType(typeof(uint), sizeof(uint), 3, 1, "global::Misaki.HighPerformance.Mathematics.uint")]
public partial struct uint3
{
}
[NumericType(typeof(uint3), sizeof(uint), 3, 2, "global::Misaki.HighPerformance.Mathematics.uint", canInverse: false, elementType: typeof(uint))]
public partial struct uint3x2
{
}
[NumericType(typeof(uint3), sizeof(uint), 3, 3, "global::Misaki.HighPerformance.Mathematics.uint", canInverse: false, elementType: typeof(uint))]
public partial struct uint3x3
{
}
[NumericType(typeof(uint3), sizeof(uint), 3, 4, "global::Misaki.HighPerformance.Mathematics.uint", canInverse: false, elementType: typeof(uint))]
public partial struct uint3x4
{
}
[NumericType(typeof(uint), sizeof(uint), 4, 1, "global::Misaki.HighPerformance.Mathematics.uint")]
public partial struct uint4
{
}
[NumericType(typeof(uint4), sizeof(uint), 4, 2, "global::Misaki.HighPerformance.Mathematics.uint", canInverse: false, elementType: typeof(uint))]
public partial struct uint4x2
{
}
[NumericType(typeof(uint4), sizeof(uint), 4, 3, "global::Misaki.HighPerformance.Mathematics.uint", canInverse: false, elementType: typeof(uint))]
public partial struct uint4x3
{
}
[NumericType(typeof(uint4), sizeof(uint), 4, 4, "global::Misaki.HighPerformance.Mathematics.uint", canInverse: false, elementType: typeof(uint))]
public partial struct uint4x4
{
}

View File

@@ -2,7 +2,7 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.Test; namespace Misaki.HighPerformance.Test.Benchmark;
[MemoryDiagnoser] [MemoryDiagnoser]
public unsafe class CollectionBenchmark public unsafe class CollectionBenchmark
@@ -34,7 +34,7 @@ public unsafe class CollectionBenchmark
array[i] = i; array[i] = i;
} }
AllocationManager.TempAllocator.Reset(); ((ArenaAllocator*)AllocationManager.TempHandle.Allocator)->Reset();
} }
[Benchmark] [Benchmark]

View File

@@ -3,7 +3,7 @@ using Misaki.HighPerformance.LowLevel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Test; namespace Misaki.HighPerformance.Test.Benchmark;
[MemoryDiagnoser] [MemoryDiagnoser]
public unsafe class FunctionPtrBenchmark public unsafe class FunctionPtrBenchmark
@@ -17,7 +17,7 @@ public unsafe class FunctionPtrBenchmark
public FunctionPtrBenchmark() public FunctionPtrBenchmark()
{ {
_addManaged = new(Marshal.GetFunctionPointerForDelegate<FunctionPointerDelegate>(Add)); _addManaged = new(Add);
_addUnmanaged = &AddUnmanaged; _addUnmanaged = &AddUnmanaged;
} }
@@ -36,7 +36,7 @@ public unsafe class FunctionPtrBenchmark
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public void InvokeManaged() public void InvokeManaged()
{ {
_sink = _addManaged.Invoke(1.0f, 2.0f); _sink = _addManaged.Delegate(1.0f, 2.0f);
} }
[Benchmark] [Benchmark]

View File

@@ -1,7 +1,7 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using System.Numerics; using System.Numerics;
namespace Misaki.HighPerformance.Test; namespace Misaki.HighPerformance.Test.Benchmark;
public class HashCodeBenchmark public class HashCodeBenchmark
{ {

View File

@@ -1,7 +1,7 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.Test; namespace Misaki.HighPerformance.Test.Benchmark;
public class HashMapBenchmark public class HashMapBenchmark
{ {

View File

@@ -0,0 +1,37 @@
using BenchmarkDotNet.Attributes;
using Misaki.HighPerformance.Mathematics;
using System.Numerics;
namespace Misaki.HighPerformance.Test.Benchmark;
public class MathematicsBenchmark
{
[Params(10, 100)]
public int count = 10;
[Benchmark(Baseline = true)]
public void Vector2Add()
{
var a = new Vector2(1, 2);
var b = new Vector2(5, 6);
var result = new Vector2();
for (var i = 0; i < count; i++)
{
result += a + b;
}
}
[Benchmark]
public void Float2Add()
{
var a = new float2(1);
var b = new float2(5);
var result = new float2();
for (var i = 0; i < count; i++)
{
result += a + b;
}
}
}

View File

@@ -1,10 +1,10 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Test.Jobs; using Misaki.HighPerformance.Test.Jobs;
using System.Numerics; using System.Numerics;
namespace Misaki.HighPerformance.Test; namespace Misaki.HighPerformance.Test.Benchmark;
[MemoryDiagnoser] [MemoryDiagnoser]
public class ParallelNoiseBenchmark public class ParallelNoiseBenchmark
@@ -13,23 +13,35 @@ public class ParallelNoiseBenchmark
private const int _HEIGHT = 512; private const int _HEIGHT = 512;
private const int _LENGTH = _WIDTH * _HEIGHT; private const int _LENGTH = _WIDTH * _HEIGHT;
[Benchmark] //[GlobalSetup]
public static void JobSystem() //public void Setup()
{ //{
using var buffers = new UnsafeArray<float>(_LENGTH, Allocator.Persistent, AllocationOption.None); // JobScheduler.Initialize();
var job = new NoiseJob() //}
{
buffers = buffers,
width = _WIDTH,
height = _HEIGHT
};
using var handle = job.Schedule(_LENGTH, 64); //[GlobalCleanup]
handle.WaitComplete(); //public void Cleanup()
} //{
// JobScheduler.Shutdown();
//}
//[Benchmark]
//public void JobSystem()
//{
// using var buffers = new UnsafeArray<float>(_LENGTH, Allocator.Persistent, AllocationOption.None);
// var job = new NoiseJob()
// {
// buffers = buffers,
// width = _WIDTH,
// height = _HEIGHT
// };
// var handle = job.Schedule(_LENGTH, 64);
// handle.Complete();
//}
[Benchmark] [Benchmark]
public static void ParallelFor() public void ParallelFor()
{ {
using var buffers = new UnsafeArray<float>(_LENGTH, Allocator.Persistent, AllocationOption.None); using var buffers = new UnsafeArray<float>(_LENGTH, Allocator.Persistent, AllocationOption.None);
@@ -42,8 +54,8 @@ public class ParallelNoiseBenchmark
}); });
} }
[Benchmark] [Benchmark(Baseline = true)]
public static void For() public void For()
{ {
using var buffers = new UnsafeArray<float>(_LENGTH, Allocator.Persistent, AllocationOption.None); using var buffers = new UnsafeArray<float>(_LENGTH, Allocator.Persistent, AllocationOption.None);
for (var i = 0; i < _LENGTH; i++) for (var i = 0; i < _LENGTH; i++)

View File

@@ -0,0 +1,184 @@
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.Test.Jobs;
/// <summary>
/// Simple job that adds a value to each element in an array.
/// </summary>
public unsafe class AddValueJob : IJobParallelFor
{
public float* Data;
public float Value;
public void Execute(int index)
{
Data[index] += Value;
}
}
/// <summary>
/// Simple job that multiplies each element in an array by a value.
/// </summary>
public unsafe class MultiplyJob : IJobParallelFor
{
public float* Data;
public float Multiplier;
public void Execute(int index)
{
Data[index] *= Multiplier;
}
}
/// <summary>
/// Simple job that computes the sum of an array (single-threaded).
/// </summary>
/// <remarks>
/// This job uses the Kahan summation algorithm to reduce numerical error.
/// </remarks>
public unsafe class KahanSumJob : IJob
{
public float* Data;
public int Length;
public float* Result;
public void Execute()
{
var sum = 0f;
var c = 0f; // Compensation for lost low-order bits
for (var i = 0; i < Length; i++)
{
var y = Data[i] - c; // So far, so good: c is zero
var t = sum + y; // Alas, sum is big, y small, so low-order digits of y are lost
c = (t - sum) - y; // (t - sum) cancels the high-order part of y; subtracting y recovers negative (low part of y)
sum = t; // Algebraically, c should always be zero. Beware overly-clever compilers!
}
*Result = sum;
}
}
/// <summary>
/// Example program demonstrating the job system with dependencies.
/// </summary>
public static class JobSystemExample
{
public static unsafe void RunExample()
{
Console.WriteLine("=== Job System Example ===");
const int arraySize = 10000;
// Create test data
using var array = new UnsafeArray<float>(arraySize, Allocator.Persistent);
// Initialize with values 1, 2, 3, ...
for (var i = 0; i < arraySize; i++)
{
array[i] = i + 1;
}
Console.WriteLine($"Initial sum: {ComputeSum((float*)array.GetUnsafePtr(), arraySize)}");
// Job 1: Add 10 to each element
var addJob = new AddValueJob
{
Data = (float*)array.GetUnsafePtr(),
Value = 10f
};
// Job 2: Multiply each element by 2 (depends on addJob)
var multiplyJob = new MultiplyJob
{
Data = (float*)array.GetUnsafePtr(),
Multiplier = 2f
};
// Job 3: Compute final sum (depends on multiplyJob)
var result = stackalloc float[1];
var sumJob = new KahanSumJob
{
Data = (float*)array.GetUnsafePtr(),
Length = arraySize,
Result = result
};
Console.WriteLine("Scheduling jobs with dependencies...");
// Schedule jobs with dependencies
var addHandle = addJob.ScheduleParallel(arraySize, 64);
var multiplyHandle = multiplyJob.ScheduleParallel(arraySize, 64, addHandle);
var sumHandle = sumJob.Schedule(multiplyHandle);
// Wait for all jobs to complete
sumHandle.Complete();
Console.WriteLine($"Final sum: {*result}");
Console.WriteLine($"Expected sum: {ComputeExpectedSum(arraySize)}");
Console.WriteLine("Jobs completed successfully!");
// Test dependency combination
Console.WriteLine("\n=== Testing Dependency Combination ===");
// Reset array
for (var i = 0; i < arraySize; i++)
{
array[i] = 1f;
}
// Create multiple independent jobs
var basePtr = (float*)array.GetUnsafePtr();
var job1 = new AddValueJob { Data = basePtr, Value = 1f };
var job2 = new AddValueJob { Data = basePtr + arraySize / 2, Value = 2f };
var job3 = new AddValueJob { Data = basePtr + arraySize / 4, Value = 3f };
var handle1 = job1.ScheduleParallel(arraySize / 2, 32);
var handle2 = job2.ScheduleParallel(arraySize / 2, 32);
var handle3 = job3.ScheduleParallel(arraySize / 4, 32);
// Combine dependencies
var combinedHandle = JobHandle.CombineDependencies(handle1, handle2, handle3);
// Final job that depends on all previous jobs
var finalSum = stackalloc float[1];
var finalSumJob = new KahanSumJob
{
Data = (float*)array.GetUnsafePtr(),
Length = arraySize,
Result = finalSum
};
var finalHandle = finalSumJob.Schedule(combinedHandle);
finalHandle.Complete();
Console.WriteLine($"Final sum after combined dependencies: {*finalSum}");
Console.WriteLine("Dependency combination test completed!");
}
private static unsafe float ComputeSum(float* data, int length)
{
var sum = 0f;
for (var i = 0; i < length; i++)
{
sum += data[i];
}
return sum;
}
private static float ComputeExpectedSum(int arraySize)
{
// Original sum: 1 + 2 + 3 + ... + n = n(n+1)/2
var originalSum = arraySize * (arraySize + 1) / 2f;
// After adding 10: each element increases by 10, so total increases by 10 * n
var afterAdd = originalSum + (10f * arraySize);
// After multiplying by 2: everything doubles
var afterMultiply = afterAdd * 2f;
return afterMultiply;
}
}

View File

@@ -4,7 +4,8 @@ using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Test.Jobs; namespace Misaki.HighPerformance.Test.Jobs;
internal struct NoiseJob : IJobParallelFor
internal unsafe struct NoiseJob : IJobParallelFor
{ {
public UnsafeArray<float> buffers; public UnsafeArray<float> buffers;
public int width; public int width;
@@ -45,6 +46,6 @@ internal struct NoiseJob : IJobParallelFor
var x = index % width; var x = index % width;
var y = index / height; var y = index / height;
var uv = new Vector2(x, y); var uv = new Vector2(x, y);
buffers[index] = GradientNoise(uv); buffers[index] = float.Clamp(GradientNoise(uv), 0.0f, 1.0f);
} }
} }

View File

@@ -10,7 +10,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" /> <PackageReference Include="BenchmarkDotNet" Version="0.15.2" />
<PackageReference Include="MSTest" Version="3.10.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -21,4 +22,8 @@
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" /> <ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="UnitTest\" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,4 +1,12 @@
using BenchmarkDotNet.Running; using Misaki.HighPerformance.Test.Benchmark;
using Misaki.HighPerformance.Test; using Misaki.HighPerformance.Test.Jobs;
BenchmarkRunner.Run<FunctionPtrBenchmark>(); // Test the job system
JobSystemExample.RunExample();
Console.WriteLine("\nPress any key to run benchmarks...");
Console.ReadKey();
BenchmarkDotNet.Running.BenchmarkRunner.Run<MathematicsBenchmark>();
//var b = new MathematicsBenchmark();
//b.Vector2Add();

View File

@@ -0,0 +1,95 @@
using Misaki.HighPerformance.Mathematics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
[TestClass]
public class TestBool2
{
[TestMethod]
public void TestConstructors()
{
// Default constructor
var v1 = new bool2();
Assert.IsFalse(v1.x);
Assert.IsFalse(v1.y);
// Single value constructor
var v2 = new bool2(true);
Assert.IsTrue(v2.x);
Assert.IsTrue(v2.y);
// Component constructor
var v3 = new bool2(true, false);
Assert.IsTrue(v3.x);
Assert.IsFalse(v3.y);
}
[TestMethod]
public void TestLogicalOperators()
{
var a = new bool2(true, false);
var b = new bool2(false, true);
// Note: bool types don't typically have bitwise operators in this implementation
// They are primarily used for conditional operations with math functions
}
[TestMethod]
public void TestComparisonOperators()
{
var a = new bool2(true, false);
var b = new bool2(true, false);
var c = new bool2(false, true);
// For bool vectors, we typically use math.all for equality comparison
var isEqual = math.all(a == b);
Assert.IsTrue(isEqual);
var isNotEqual = math.any(a != c);
Assert.IsTrue(isNotEqual);
}
[TestMethod]
public void TestSwizzleProperties()
{
var v = new bool2(true, false);
Assert.IsTrue(v.x);
Assert.IsFalse(v.y);
var xy = v.xy;
Assert.IsTrue(xy.x);
Assert.IsFalse(xy.y);
}
[TestMethod]
public void TestMathFunctions()
{
var v = new bool2(true, false);
// Test any function
var anyResult = math.any(v);
Assert.IsTrue(anyResult);
var allFalse = new bool2(false, false);
var anyFalse = math.any(allFalse);
Assert.IsFalse(anyFalse);
// Test all function
var allResult = math.all(v);
Assert.IsFalse(allResult);
var allTrue = new bool2(true, true);
var allTrueResult = math.all(allTrue);
Assert.IsTrue(allTrueResult);
}
[TestMethod]
public void TestIndexer()
{
var v = new bool2(true, false);
Assert.IsTrue(v[0]);
Assert.IsFalse(v[1]);
}
}

View File

@@ -0,0 +1,123 @@
using Misaki.HighPerformance.Mathematics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
[TestClass]
public class TestDouble2
{
[TestMethod]
public void TestConstructors()
{
// Default constructor
var v1 = new double2();
Assert.AreEqual(0.0, v1.x, 1e-15);
Assert.AreEqual(0.0, v1.y, 1e-15);
// Single value constructor
var v2 = new double2(5.5);
Assert.AreEqual(5.5, v2.x, 1e-15);
Assert.AreEqual(5.5, v2.y, 1e-15);
// Component constructor
var v3 = new double2(1.5, 2.5);
Assert.AreEqual(1.5, v3.x, 1e-15);
Assert.AreEqual(2.5, v3.y, 1e-15);
}
[TestMethod]
public void TestArithmeticOperators()
{
var a = new double2(10.5, 20.5);
var b = new double2(5.5, 4.5);
// Addition
var add = a + b;
Assert.AreEqual(16.0, add.x, 1e-15);
Assert.AreEqual(25.0, add.y, 1e-15);
// Subtraction
var sub = a - b;
Assert.AreEqual(5.0, sub.x, 1e-15);
Assert.AreEqual(16.0, sub.y, 1e-15);
// Multiplication
var mul = a * b;
Assert.AreEqual(57.75, mul.x, 1e-15);
Assert.AreEqual(92.25, mul.y, 1e-15);
// Division
var div = a / b;
Assert.AreEqual(10.5 / 5.5, div.x, 1e-15);
Assert.AreEqual(20.5 / 4.5, div.y, 1e-15);
// Scalar operations
var scalarMul = a * 2.0;
Assert.AreEqual(21.0, scalarMul.x, 1e-15);
Assert.AreEqual(41.0, scalarMul.y, 1e-15);
var scalarDiv = a / 2.0;
Assert.AreEqual(5.25, scalarDiv.x, 1e-15);
Assert.AreEqual(10.25, scalarDiv.y, 1e-15);
}
[TestMethod]
public void TestComparisonOperators()
{
var a = new double2(10.5, 20.5);
var b = new double2(10.5, 20.5);
var c = new double2(5.5, 30.5);
// Equality (approximate for floating point)
Assert.IsTrue(math.all(math.abs(a - b) < 1e-15));
Assert.IsFalse(math.all(math.abs(a - c) < 1e-15));
}
[TestMethod]
public void TestSwizzleProperties()
{
var v = new double2(1.5, 2.5);
Assert.AreEqual(1.5, v.x, 1e-15);
Assert.AreEqual(2.5, v.y, 1e-15);
// Test common swizzles
var xy = v.xy;
Assert.AreEqual(1.5, xy.x, 1e-15);
Assert.AreEqual(2.5, xy.y, 1e-15);
}
[TestMethod]
public void TestUnaryOperators()
{
var a = new double2(5.5, -3.5);
// Unary minus
var neg = -a;
Assert.AreEqual(-5.5, neg.x, 1e-15);
Assert.AreEqual(3.5, neg.y, 1e-15);
// Unary plus
var pos = +a;
Assert.AreEqual(5.5, pos.x, 1e-15);
Assert.AreEqual(-3.5, pos.y, 1e-15);
}
[TestMethod]
public void TestMathFunctions()
{
var v = new double2(3.0, 4.0);
// Test dot product
var dot = math.dot(v, v);
Assert.AreEqual(25.0, dot, 1e-15);
// Test length
var length = math.length(v);
Assert.AreEqual(5.0, length, 1e-15);
// Test normalize
var normalized = math.normalize(v);
var expectedLength = math.length(normalized);
Assert.AreEqual(1.0, expectedLength, 1e-15);
}
}

View File

@@ -0,0 +1,139 @@
using Misaki.HighPerformance.Mathematics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
[TestClass]
public class TestFloat2
{
[TestMethod]
public void TestConstructors()
{
// Default constructor
var v1 = new float2();
Assert.AreEqual(0f, v1.x, 1e-6f);
Assert.AreEqual(0f, v1.y, 1e-6f);
// Single value constructor
var v2 = new float2(5.5f);
Assert.AreEqual(5.5f, v2.x, 1e-6f);
Assert.AreEqual(5.5f, v2.y, 1e-6f);
// Component constructor
var v3 = new float2(1.5f, 2.5f);
Assert.AreEqual(1.5f, v3.x, 1e-6f);
Assert.AreEqual(2.5f, v3.y, 1e-6f);
}
[TestMethod]
public void TestArithmeticOperators()
{
var a = new float2(10.5f, 20.5f);
var b = new float2(5.5f, 4.5f);
// Addition
var add = a + b;
Assert.AreEqual(16f, add.x, 1e-6f);
Assert.AreEqual(25f, add.y, 1e-6f);
// Subtraction
var sub = a - b;
Assert.AreEqual(5f, sub.x, 1e-6f);
Assert.AreEqual(16f, sub.y, 1e-6f);
// Multiplication
var mul = a * b;
Assert.AreEqual(57.75f, mul.x, 1e-6f);
Assert.AreEqual(92.25f, mul.y, 1e-6f);
// Division
var div = a / b;
Assert.AreEqual(10.5f / 5.5f, div.x, 1e-6f);
Assert.AreEqual(20.5f / 4.5f, div.y, 1e-6f);
// Scalar operations
var scalarMul = a * 2f;
Assert.AreEqual(21f, scalarMul.x, 1e-6f);
Assert.AreEqual(41f, scalarMul.y, 1e-6f);
var scalarDiv = a / 2f;
Assert.AreEqual(5.25f, scalarDiv.x, 1e-6f);
Assert.AreEqual(10.25f, scalarDiv.y, 1e-6f);
}
[TestMethod]
public void TestComparisonOperators()
{
var a = new float2(10.5f, 20.5f);
var b = new float2(10.5f, 20.5f);
var c = new float2(5.5f, 30.5f);
// Equality (approximate for floating point)
Assert.IsTrue(math.all(math.abs(a - b) < 1e-6f));
Assert.IsFalse(math.all(math.abs(a - c) < 1e-6f));
}
[TestMethod]
public void TestSwizzleProperties()
{
var v = new float2(1.5f, 2.5f);
Assert.AreEqual(1.5f, v.x, 1e-6f);
Assert.AreEqual(2.5f, v.y, 1e-6f);
var xy = v.xy;
Assert.AreEqual(1.5f, xy.x, 1e-6f);
Assert.AreEqual(2.5f, xy.y, 1e-6f);
}
[TestMethod]
public void TestStaticProperties()
{
var zero = float2.zero;
Assert.AreEqual(0f, zero.x, 1e-6f);
Assert.AreEqual(0f, zero.y, 1e-6f);
}
[TestMethod]
public void TestUnaryOperators()
{
var a = new float2(5.5f, -3.5f);
// Unary minus
var neg = -a;
Assert.AreEqual(-5.5f, neg.x, 1e-6f);
Assert.AreEqual(3.5f, neg.y, 1e-6f);
// Unary plus
var pos = +a;
Assert.AreEqual(5.5f, pos.x, 1e-6f);
Assert.AreEqual(-3.5f, pos.y, 1e-6f);
}
[TestMethod]
public void TestMathFunctions()
{
var v = new float2(3f, 4f);
// Test dot product
var dot = math.dot(v, v);
Assert.AreEqual(25f, dot, 1e-6f);
// Test length
var length = math.length(v);
Assert.AreEqual(5f, length, 1e-6f);
// Test normalize
var normalized = math.normalize(v);
var expectedLength = math.length(normalized);
Assert.AreEqual(1f, expectedLength, 1e-6f);
}
[TestMethod]
public void TestIndexer()
{
var v = new float2(10.5f, 20.5f);
Assert.AreEqual(10.5f, v[0], 1e-6f);
Assert.AreEqual(20.5f, v[1], 1e-6f);
}
}

View File

@@ -0,0 +1,177 @@
using Misaki.HighPerformance.Mathematics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
[TestClass]
public class TestFloat3
{
[TestMethod]
public void TestConstructors()
{
// Default constructor
var v1 = new float3();
Assert.AreEqual(0f, v1.x, 1e-6f);
Assert.AreEqual(0f, v1.y, 1e-6f);
Assert.AreEqual(0f, v1.z, 1e-6f);
// Single value constructor
var v2 = new float3(5.5f);
Assert.AreEqual(5.5f, v2.x, 1e-6f);
Assert.AreEqual(5.5f, v2.y, 1e-6f);
Assert.AreEqual(5.5f, v2.z, 1e-6f);
// Component constructor
var v3 = new float3(1.5f, 2.5f, 3.5f);
Assert.AreEqual(1.5f, v3.x, 1e-6f);
Assert.AreEqual(2.5f, v3.y, 1e-6f);
Assert.AreEqual(3.5f, v3.z, 1e-6f);
// Mixed constructors
var v4 = new float3(new float2(1.5f, 2.5f), 3.5f);
Assert.AreEqual(1.5f, v4.x, 1e-6f);
Assert.AreEqual(2.5f, v4.y, 1e-6f);
Assert.AreEqual(3.5f, v4.z, 1e-6f);
}
[TestMethod]
public void TestArithmeticOperators()
{
var a = new float3(10.5f, 20.5f, 30.5f);
var b = new float3(5.5f, 4.5f, 6.5f);
// Addition
var add = a + b;
Assert.AreEqual(16f, add.x, 1e-6f);
Assert.AreEqual(25f, add.y, 1e-6f);
Assert.AreEqual(37f, add.z, 1e-6f);
// Subtraction
var sub = a - b;
Assert.AreEqual(5f, sub.x, 1e-6f);
Assert.AreEqual(16f, sub.y, 1e-6f);
Assert.AreEqual(24f, sub.z, 1e-6f);
// Multiplication
var mul = a * b;
Assert.AreEqual(57.75f, mul.x, 1e-6f);
Assert.AreEqual(92.25f, mul.y, 1e-6f);
Assert.AreEqual(198.25f, mul.z, 1e-6f);
// Scalar operations
var scalarMul = a * 2f;
Assert.AreEqual(21f, scalarMul.x, 1e-6f);
Assert.AreEqual(41f, scalarMul.y, 1e-6f);
Assert.AreEqual(61f, scalarMul.z, 1e-6f);
}
[TestMethod]
public void TestVectorOperations()
{
var a = new float3(1f, 0f, 0f);
var b = new float3(0f, 1f, 0f);
// Cross product
var cross = math.cross(a, b);
Assert.AreEqual(0f, cross.x, 1e-6f);
Assert.AreEqual(0f, cross.y, 1e-6f);
Assert.AreEqual(1f, cross.z, 1e-6f);
// Dot product
var dot = math.dot(a, b);
Assert.AreEqual(0f, dot, 1e-6f);
// Test with non-orthogonal vectors
var c = new float3(3f, 4f, 0f);
var d = new float3(1f, 1f, 1f);
var dotCD = math.dot(c, d);
Assert.AreEqual(7f, dotCD, 1e-6f); // 3*1 + 4*1 + 0*1 = 7
}
[TestMethod]
public void TestComparisonOperators()
{
var a = new float3(10.5f, 20.5f, 30.5f);
var b = new float3(10.5f, 20.5f, 30.5f);
var c = new float3(5.5f, 30.5f, 25.5f);
// Equality (approximate for floating point)
Assert.IsTrue(math.all(math.abs(a - b) < 1e-6f));
Assert.IsFalse(math.all(math.abs(a - c) < 1e-6f));
}
[TestMethod]
public void TestSwizzleProperties()
{
var v = new float3(1.5f, 2.5f, 3.5f);
Assert.AreEqual(1.5f, v.x, 1e-6f);
Assert.AreEqual(2.5f, v.y, 1e-6f);
Assert.AreEqual(3.5f, v.z, 1e-6f);
var xy = v.xy;
Assert.AreEqual(1.5f, xy.x, 1e-6f);
Assert.AreEqual(2.5f, xy.y, 1e-6f);
var xyz = v.xyz;
Assert.AreEqual(1.5f, xyz.x, 1e-6f);
Assert.AreEqual(2.5f, xyz.y, 1e-6f);
Assert.AreEqual(3.5f, xyz.z, 1e-6f);
}
[TestMethod]
public void TestUnaryOperators()
{
var a = new float3(5.5f, -3.5f, 7.5f);
// Unary minus
var neg = -a;
Assert.AreEqual(-5.5f, neg.x, 1e-6f);
Assert.AreEqual(3.5f, neg.y, 1e-6f);
Assert.AreEqual(-7.5f, neg.z, 1e-6f);
// Unary plus
var pos = +a;
Assert.AreEqual(5.5f, pos.x, 1e-6f);
Assert.AreEqual(-3.5f, pos.y, 1e-6f);
Assert.AreEqual(7.5f, pos.z, 1e-6f);
}
[TestMethod]
public void TestMathFunctions()
{
var v = new float3(3f, 4f, 0f);
// Test length
var length = math.length(v);
Assert.AreEqual(5f, length, 1e-6f);
// Test normalize
var normalized = math.normalize(v);
var expectedLength = math.length(normalized);
Assert.AreEqual(1f, expectedLength, 1e-6f);
Assert.AreEqual(0.6f, normalized.x, 1e-6f);
Assert.AreEqual(0.8f, normalized.y, 1e-6f);
Assert.AreEqual(0f, normalized.z, 1e-6f);
}
[TestMethod]
public void TestStaticValues()
{
var zero = float3.zero;
Assert.AreEqual(0f, zero.x, 1e-6f);
Assert.AreEqual(0f, zero.y, 1e-6f);
Assert.AreEqual(0f, zero.z, 1e-6f);
var right = new float3(1f, 0f, 0f);
var up = new float3(0f, 1f, 0f);
var forward = new float3(0f, 0f, 1f);
// These are common vector directions
Assert.AreEqual(1f, right.x, 1e-6f);
Assert.AreEqual(1f, up.y, 1e-6f);
Assert.AreEqual(1f, forward.z, 1e-6f);
}
}

View File

@@ -0,0 +1,200 @@
using Misaki.HighPerformance.Mathematics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
[TestClass]
public class TestFloat4
{
[TestMethod]
public void TestConstructors()
{
// Default constructor
var v1 = new float4();
Assert.AreEqual(0f, v1.x, 1e-6f);
Assert.AreEqual(0f, v1.y, 1e-6f);
Assert.AreEqual(0f, v1.z, 1e-6f);
Assert.AreEqual(0f, v1.w, 1e-6f);
// Single value constructor
var v2 = new float4(5.5f);
Assert.AreEqual(5.5f, v2.x, 1e-6f);
Assert.AreEqual(5.5f, v2.y, 1e-6f);
Assert.AreEqual(5.5f, v2.z, 1e-6f);
Assert.AreEqual(5.5f, v2.w, 1e-6f);
// Component constructor
var v3 = new float4(1.5f, 2.5f, 3.5f, 4.5f);
Assert.AreEqual(1.5f, v3.x, 1e-6f);
Assert.AreEqual(2.5f, v3.y, 1e-6f);
Assert.AreEqual(3.5f, v3.z, 1e-6f);
Assert.AreEqual(4.5f, v3.w, 1e-6f);
// Mixed constructors
var v4 = new float4(new float2(1.5f, 2.5f), 3.5f, 4.5f);
Assert.AreEqual(1.5f, v4.x, 1e-6f);
Assert.AreEqual(2.5f, v4.y, 1e-6f);
Assert.AreEqual(3.5f, v4.z, 1e-6f);
Assert.AreEqual(4.5f, v4.w, 1e-6f);
var v5 = new float4(new float3(1.5f, 2.5f, 3.5f), 4.5f);
Assert.AreEqual(1.5f, v5.x, 1e-6f);
Assert.AreEqual(2.5f, v5.y, 1e-6f);
Assert.AreEqual(3.5f, v5.z, 1e-6f);
Assert.AreEqual(4.5f, v5.w, 1e-6f);
}
[TestMethod]
public void TestArithmeticOperators()
{
var a = new float4(10.5f, 20.5f, 30.5f, 40.5f);
var b = new float4(5.5f, 4.5f, 6.5f, 8.5f);
// Addition
var add = a + b;
Assert.AreEqual(16f, add.x, 1e-6f);
Assert.AreEqual(25f, add.y, 1e-6f);
Assert.AreEqual(37f, add.z, 1e-6f);
Assert.AreEqual(49f, add.w, 1e-6f);
// Subtraction
var sub = a - b;
Assert.AreEqual(5f, sub.x, 1e-6f);
Assert.AreEqual(16f, sub.y, 1e-6f);
Assert.AreEqual(24f, sub.z, 1e-6f);
Assert.AreEqual(32f, sub.w, 1e-6f);
// Multiplication
var mul = a * b;
Assert.AreEqual(57.75f, mul.x, 1e-6f);
Assert.AreEqual(92.25f, mul.y, 1e-6f);
Assert.AreEqual(198.25f, mul.z, 1e-6f);
Assert.AreEqual(344.25f, mul.w, 1e-6f);
// Scalar operations
var scalarMul = a * 2f;
Assert.AreEqual(21f, scalarMul.x, 1e-6f);
Assert.AreEqual(41f, scalarMul.y, 1e-6f);
Assert.AreEqual(61f, scalarMul.z, 1e-6f);
Assert.AreEqual(81f, scalarMul.w, 1e-6f);
}
[TestMethod]
public void TestVectorOperations()
{
var a = new float4(1f, 2f, 3f, 4f);
var b = new float4(5f, 6f, 7f, 8f);
// Dot product
var dot = math.dot(a, b);
Assert.AreEqual(70f, dot, 1e-6f); // 1*5 + 2*6 + 3*7 + 4*8 = 5+12+21+32 = 70
}
[TestMethod]
public void TestComparisonOperators()
{
var a = new float4(10.5f, 20.5f, 30.5f, 40.5f);
var b = new float4(10.5f, 20.5f, 30.5f, 40.5f);
var c = new float4(5.5f, 30.5f, 25.5f, 45.5f);
// Equality (approximate for floating point)
Assert.IsTrue(math.all(math.abs(a - b) < 1e-6f));
Assert.IsFalse(math.all(math.abs(a - c) < 1e-6f));
}
[TestMethod]
public void TestSwizzleProperties()
{
var v = new float4(1.5f, 2.5f, 3.5f, 4.5f);
Assert.AreEqual(1.5f, v.x, 1e-6f);
Assert.AreEqual(2.5f, v.y, 1e-6f);
Assert.AreEqual(3.5f, v.z, 1e-6f);
Assert.AreEqual(4.5f, v.w, 1e-6f);
var xy = v.xy;
Assert.AreEqual(1.5f, xy.x, 1e-6f);
Assert.AreEqual(2.5f, xy.y, 1e-6f);
var xyz = v.xyz;
Assert.AreEqual(1.5f, xyz.x, 1e-6f);
Assert.AreEqual(2.5f, xyz.y, 1e-6f);
Assert.AreEqual(3.5f, xyz.z, 1e-6f);
var xyzw = v.xyzw;
Assert.AreEqual(1.5f, xyzw.x, 1e-6f);
Assert.AreEqual(2.5f, xyzw.y, 1e-6f);
Assert.AreEqual(3.5f, xyzw.z, 1e-6f);
Assert.AreEqual(4.5f, xyzw.w, 1e-6f);
}
[TestMethod]
public void TestUnaryOperators()
{
var a = new float4(5.5f, -3.5f, 7.5f, -9.5f);
// Unary minus
var neg = -a;
Assert.AreEqual(-5.5f, neg.x, 1e-6f);
Assert.AreEqual(3.5f, neg.y, 1e-6f);
Assert.AreEqual(-7.5f, neg.z, 1e-6f);
Assert.AreEqual(9.5f, neg.w, 1e-6f);
// Unary plus
var pos = +a;
Assert.AreEqual(5.5f, pos.x, 1e-6f);
Assert.AreEqual(-3.5f, pos.y, 1e-6f);
Assert.AreEqual(7.5f, pos.z, 1e-6f);
Assert.AreEqual(-9.5f, pos.w, 1e-6f);
}
[TestMethod]
public void TestMathFunctions()
{
var v = new float4(1f, 2f, 2f, 0f);
// Test length
var length = math.length(v);
Assert.AreEqual(3f, length, 1e-6f); // sqrt(1+4+4+0) = sqrt(9) = 3
// Test normalize
var normalized = math.normalize(v);
var expectedLength = math.length(normalized);
Assert.AreEqual(1f, expectedLength, 1e-6f);
Assert.AreEqual(1f / 3f, normalized.x, 1e-6f);
Assert.AreEqual(2f / 3f, normalized.y, 1e-6f);
Assert.AreEqual(2f / 3f, normalized.z, 1e-6f);
Assert.AreEqual(0f, normalized.w, 1e-6f);
}
[TestMethod]
public void TestStaticValues()
{
var zero = float4.zero;
Assert.AreEqual(0f, zero.x, 1e-6f);
Assert.AreEqual(0f, zero.y, 1e-6f);
Assert.AreEqual(0f, zero.z, 1e-6f);
Assert.AreEqual(0f, zero.w, 1e-6f);
// Test unit vectors
var unitX = new float4(1f, 0f, 0f, 0f);
var unitY = new float4(0f, 1f, 0f, 0f);
var unitZ = new float4(0f, 0f, 1f, 0f);
var unitW = new float4(0f, 0f, 0f, 1f);
Assert.AreEqual(1f, unitX.x, 1e-6f);
Assert.AreEqual(1f, unitY.y, 1e-6f);
Assert.AreEqual(1f, unitZ.z, 1e-6f);
Assert.AreEqual(1f, unitW.w, 1e-6f);
}
[TestMethod]
public void TestIndexer()
{
var v = new float4(10.5f, 20.5f, 30.5f, 40.5f);
Assert.AreEqual(10.5f, v[0], 1e-6f);
Assert.AreEqual(20.5f, v[1], 1e-6f);
Assert.AreEqual(30.5f, v[2], 1e-6f);
Assert.AreEqual(40.5f, v[3], 1e-6f);
}
}

View File

@@ -0,0 +1,224 @@
using Misaki.HighPerformance.Mathematics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
[TestClass]
public class TestFloat4x4
{
[TestMethod]
public void TestConstructors()
{
// Default constructor
var m1 = new float4x4();
var identity = float4x4.identity;
Assert.AreEqual(1f, identity.c0.x, 1e-6f);
Assert.AreEqual(0f, identity.c0.y, 1e-6f);
Assert.AreEqual(0f, identity.c0.z, 1e-6f);
Assert.AreEqual(0f, identity.c0.w, 1e-6f);
Assert.AreEqual(0f, identity.c1.x, 1e-6f);
Assert.AreEqual(1f, identity.c1.y, 1e-6f);
Assert.AreEqual(0f, identity.c1.z, 1e-6f);
Assert.AreEqual(0f, identity.c1.w, 1e-6f);
// Column constructor
var c0 = new float4(1f, 0f, 0f, 0f);
var c1 = new float4(0f, 1f, 0f, 0f);
var c2 = new float4(0f, 0f, 1f, 0f);
var c3 = new float4(0f, 0f, 0f, 1f);
var m2 = new float4x4(c0, c1, c2, c3);
Assert.AreEqual(1f, m2.c0.x, 1e-6f);
Assert.AreEqual(1f, m2.c1.y, 1e-6f);
Assert.AreEqual(1f, m2.c2.z, 1e-6f);
Assert.AreEqual(1f, m2.c3.w, 1e-6f);
}
[TestMethod]
public void TestArithmeticOperators()
{
var a = new float4x4(
new float4(1f, 2f, 3f, 4f),
new float4(5f, 6f, 7f, 8f),
new float4(9f, 10f, 11f, 12f),
new float4(13f, 14f, 15f, 16f)
);
var b = new float4x4(
new float4(1f, 1f, 1f, 1f),
new float4(1f, 1f, 1f, 1f),
new float4(1f, 1f, 1f, 1f),
new float4(1f, 1f, 1f, 1f)
);
// Addition
var add = a + b;
Assert.AreEqual(2f, add.c0.x, 1e-6f);
Assert.AreEqual(3f, add.c0.y, 1e-6f);
Assert.AreEqual(6f, add.c1.x, 1e-6f);
Assert.AreEqual(7f, add.c1.y, 1e-6f);
// Subtraction
var sub = a - b;
Assert.AreEqual(0f, sub.c0.x, 1e-6f);
Assert.AreEqual(1f, sub.c0.y, 1e-6f);
Assert.AreEqual(4f, sub.c1.x, 1e-6f);
Assert.AreEqual(5f, sub.c1.y, 1e-6f);
// Scalar multiplication
var scalarMul = a * 2f;
Assert.AreEqual(2f, scalarMul.c0.x, 1e-6f);
Assert.AreEqual(4f, scalarMul.c0.y, 1e-6f);
Assert.AreEqual(10f, scalarMul.c1.x, 1e-6f);
Assert.AreEqual(12f, scalarMul.c1.y, 1e-6f);
}
[TestMethod]
public void TestMatrixMultiplication()
{
// Identity matrix multiplication
var identity = new float4x4(
new float4(1f, 0f, 0f, 0f),
new float4(0f, 1f, 0f, 0f),
new float4(0f, 0f, 1f, 0f),
new float4(0f, 0f, 0f, 1f)
);
var testMatrix = new float4x4(
new float4(2f, 3f, 4f, 5f),
new float4(6f, 7f, 8f, 9f),
new float4(10f, 11f, 12f, 13f),
new float4(14f, 15f, 16f, 17f)
);
// Matrix multiplication with identity should return original matrix
var result = math.mul(identity, testMatrix);
Assert.AreEqual(testMatrix.c0.x, result.c0.x, 1e-6f);
Assert.AreEqual(testMatrix.c0.y, result.c0.y, 1e-6f);
Assert.AreEqual(testMatrix.c1.x, result.c1.x, 1e-6f);
Assert.AreEqual(testMatrix.c1.y, result.c1.y, 1e-6f);
}
[TestMethod]
public void TestMatrixVectorMultiplication()
{
var matrix = new float4x4(
new float4(1f, 0f, 0f, 0f),
new float4(0f, 1f, 0f, 0f),
new float4(0f, 0f, 1f, 0f),
new float4(0f, 0f, 0f, 1f)
);
var vector = new float4(1f, 2f, 3f, 1f);
// Identity matrix multiplication should return original vector
var result = math.mul(matrix, vector);
Assert.AreEqual(1f, result.x, 1e-6f);
Assert.AreEqual(2f, result.y, 1e-6f);
Assert.AreEqual(3f, result.z, 1e-6f);
Assert.AreEqual(1f, result.w, 1e-6f);
}
[TestMethod]
public void TestTranspose()
{
var matrix = new float4x4(
new float4(1f, 2f, 3f, 4f),
new float4(5f, 6f, 7f, 8f),
new float4(9f, 10f, 11f, 12f),
new float4(13f, 14f, 15f, 16f)
);
var transposed = math.transpose(matrix);
// Check that rows and columns are swapped
Assert.AreEqual(matrix.c0.x, transposed.c0.x, 1e-6f); // (0,0) stays same
Assert.AreEqual(matrix.c1.x, transposed.c0.y, 1e-6f); // (1,0) becomes (0,1)
Assert.AreEqual(matrix.c2.x, transposed.c0.z, 1e-6f); // (2,0) becomes (0,2)
Assert.AreEqual(matrix.c3.x, transposed.c0.w, 1e-6f); // (3,0) becomes (0,3)
Assert.AreEqual(matrix.c0.y, transposed.c1.x, 1e-6f); // (0,1) becomes (1,0)
Assert.AreEqual(matrix.c1.y, transposed.c1.y, 1e-6f); // (1,1) stays same
}
[TestMethod]
public void TestDeterminant()
{
// Test determinant of identity matrix (should be 1)
var identity = new float4x4(
new float4(1f, 0f, 0f, 0f),
new float4(0f, 1f, 0f, 0f),
new float4(0f, 0f, 1f, 0f),
new float4(0f, 0f, 0f, 1f)
);
var det = math.determinant(identity);
Assert.AreEqual(1f, det, 1e-6f);
}
[TestMethod]
public void TestInverse()
{
// Test inverse of identity matrix (should be identity)
var identity = new float4x4(
new float4(1f, 0f, 0f, 0f),
new float4(0f, 1f, 0f, 0f),
new float4(0f, 0f, 1f, 0f),
new float4(0f, 0f, 0f, 1f)
);
var inverse = math.inverse(identity);
Assert.AreEqual(1f, inverse.c0.x, 1e-6f);
Assert.AreEqual(0f, inverse.c0.y, 1e-6f);
Assert.AreEqual(0f, inverse.c1.x, 1e-6f);
Assert.AreEqual(1f, inverse.c1.y, 1e-6f);
Assert.AreEqual(1f, inverse.c2.z, 1e-6f);
Assert.AreEqual(1f, inverse.c3.w, 1e-6f);
}
[TestMethod]
public void TestComparisonOperators()
{
var a = new float4x4(
new float4(1f, 2f, 3f, 4f),
new float4(5f, 6f, 7f, 8f),
new float4(9f, 10f, 11f, 12f),
new float4(13f, 14f, 15f, 16f)
);
var b = new float4x4(
new float4(1f, 2f, 3f, 4f),
new float4(5f, 6f, 7f, 8f),
new float4(9f, 10f, 11f, 12f),
new float4(13f, 14f, 15f, 16f)
);
// Test equality
var isEqual = math.all(a.c0 == b.c0) &&
math.all(a.c1 == b.c1) &&
math.all(a.c2 == b.c2) &&
math.all(a.c3 == b.c3);
Assert.IsTrue(isEqual);
}
[TestMethod]
public void TestIndexer()
{
var matrix = new float4x4(
new float4(1f, 2f, 3f, 4f),
new float4(5f, 6f, 7f, 8f),
new float4(9f, 10f, 11f, 12f),
new float4(13f, 14f, 15f, 16f)
);
// Test column access
Assert.AreEqual(1f, matrix[0].x, 1e-6f);
Assert.AreEqual(2f, matrix[0].y, 1e-6f);
Assert.AreEqual(5f, matrix[1].x, 1e-6f);
Assert.AreEqual(6f, matrix[1].y, 1e-6f);
}
}

View File

@@ -0,0 +1,171 @@
using Misaki.HighPerformance.Mathematics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
[TestClass]
public class TestInt2
{
[TestMethod]
public void TestConstructors()
{
// Default constructor
var v1 = new int2();
Assert.AreEqual(0, v1.x);
Assert.AreEqual(0, v1.y);
// Single value constructor
var v2 = new int2(5);
Assert.AreEqual(5, v2.x);
Assert.AreEqual(5, v2.y);
// Component constructor
var v3 = new int2(1, 2);
Assert.AreEqual(1, v3.x);
Assert.AreEqual(2, v3.y);
}
[TestMethod]
public void TestArithmeticOperators()
{
var a = new int2(10, 20);
var b = new int2(5, 4);
// Addition
var add = a + b;
Assert.AreEqual(15, add.x);
Assert.AreEqual(24, add.y);
// Subtraction
var sub = a - b;
Assert.AreEqual(5, sub.x);
Assert.AreEqual(16, sub.y);
// Multiplication
var mul = a * b;
Assert.AreEqual(50, mul.x);
Assert.AreEqual(80, mul.y);
// Division
var div = a / b;
Assert.AreEqual(2, div.x);
Assert.AreEqual(5, div.y);
// Scalar operations
var scalarMul = a * 2;
Assert.AreEqual(20, scalarMul.x);
Assert.AreEqual(40, scalarMul.y);
var scalarDiv = a / 2;
Assert.AreEqual(5, scalarDiv.x);
Assert.AreEqual(10, scalarDiv.y);
}
[TestMethod]
public void TestBitwiseOperators()
{
var a = new int2(0b1010, 0b1100);
var b = new int2(0b1100, 0b1010);
// Bitwise AND
var and = a & b;
Assert.AreEqual(0b1000, and.x);
Assert.AreEqual(0b1000, and.y);
// Bitwise OR
var or = a | b;
Assert.AreEqual(0b1110, or.x);
Assert.AreEqual(0b1110, or.y);
// Bitwise XOR
var xor = a ^ b;
Assert.AreEqual(0b0110, xor.x);
Assert.AreEqual(0b0110, xor.y);
// Bitwise NOT
var not = ~a;
Assert.AreEqual(~0b1010, not.x);
Assert.AreEqual(~0b1100, not.y);
}
[TestMethod]
public void TestShiftOperators()
{
var a = new int2(8, 16);
// Left shift
var leftShift = a << 1;
Assert.AreEqual(16, leftShift.x);
Assert.AreEqual(32, leftShift.y);
// Right shift
var rightShift = a >> 1;
Assert.AreEqual(4, rightShift.x);
Assert.AreEqual(8, rightShift.y);
}
[TestMethod]
public void TestComparisonOperators()
{
var a = new int2(10, 20);
var b = new int2(10, 20);
var c = new int2(5, 30);
// Equality
Assert.IsTrue(math.all(a == b));
Assert.IsFalse(math.all(a == c));
// Inequality
Assert.IsFalse(math.all(a != b));
Assert.IsTrue(math.all(a != c));
}
[TestMethod]
public void TestSwizzleProperties()
{
var v = new int2(1, 2);
// Test common swizzles if they exist
Assert.AreEqual(1, v.x);
Assert.AreEqual(2, v.y);
var xy = v.xy;
Assert.AreEqual(1, xy.x);
Assert.AreEqual(2, xy.y);
}
[TestMethod]
public void TestStaticProperties()
{
var zero = int2.zero;
Assert.AreEqual(0, zero.x);
Assert.AreEqual(0, zero.y);
var one = int2.one;
Assert.AreEqual(1, one.x);
Assert.AreEqual(1, one.y);
}
[TestMethod]
public void TestUnaryOperators()
{
var a = new int2(5, -3);
// Unary minus
var neg = -a;
Assert.AreEqual(-5, neg.x);
Assert.AreEqual(3, neg.y);
// Unary plus
var pos = +a;
Assert.AreEqual(5, pos.x);
Assert.AreEqual(-3, pos.y);
}
[TestMethod]
public void TestIndexer()
{
var v = new int2(10, 20);
Assert.AreEqual(10, v[0]);
Assert.AreEqual(20, v[1]);
}
}

View File

@@ -0,0 +1,198 @@
using Misaki.HighPerformance.Mathematics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
[TestClass]
public class TestInt2x2
{
[TestMethod]
public void TestConstructors()
{
// Default constructor
var m1 = new int2x2();
// Column constructor
var c0 = new int2(1, 2);
var c1 = new int2(3, 4);
var m2 = new int2x2(c0, c1);
Assert.AreEqual(1, m2.c0.x);
Assert.AreEqual(2, m2.c0.y);
Assert.AreEqual(3, m2.c1.x);
Assert.AreEqual(4, m2.c1.y);
var m3 = new int2x2(1, 2, 3, 4);
Assert.AreEqual(1, m3.c0.x);
Assert.AreEqual(2, m3.c1.x);
Assert.AreEqual(3, m3.c0.y);
Assert.AreEqual(4, m3.c1.y);
}
[TestMethod]
public void TestArithmeticOperators()
{
var a = new int2x2(
new int2(1, 2),
new int2(3, 4)
);
var b = new int2x2(
new int2(5, 6),
new int2(7, 8)
);
// Addition
var add = a + b;
Assert.AreEqual(6, add.c0.x);
Assert.AreEqual(8, add.c0.y);
Assert.AreEqual(10, add.c1.x);
Assert.AreEqual(12, add.c1.y);
// Subtraction
var sub = b - a;
Assert.AreEqual(4, sub.c0.x);
Assert.AreEqual(4, sub.c0.y);
Assert.AreEqual(4, sub.c1.x);
Assert.AreEqual(4, sub.c1.y);
// Scalar multiplication
var scalarMul = a * 2;
Assert.AreEqual(2, scalarMul.c0.x);
Assert.AreEqual(4, scalarMul.c0.y);
Assert.AreEqual(6, scalarMul.c1.x);
Assert.AreEqual(8, scalarMul.c1.y);
}
[TestMethod]
public void TestMatrixMultiplication()
{
var a = new int2x2(
new int2(1, 2),
new int2(3, 4)
);
var b = new int2x2(
new int2(5, 6),
new int2(7, 8)
);
// Matrix multiplication
var mul = math.mul(a, b);
// Expected result: [1 3] * [5 7] = [1*5+3*6 1*7+3*8] = [23 31]
// [2 4] [6 8] [2*5+4*6 2*7+4*8] [34 46]
Assert.AreEqual(23, mul.c0.x);
Assert.AreEqual(34, mul.c0.y);
Assert.AreEqual(31, mul.c1.x);
Assert.AreEqual(46, mul.c1.y);
}
[TestMethod]
public void TestMatrixVectorMultiplication()
{
var matrix = new int2x2(
new int2(1, 2),
new int2(3, 4)
);
var vector = new int2(5, 6);
// Matrix-vector multiplication
var result = math.mul(matrix, vector);
// Expected: [1 3] * [5] = [1*5 + 3*6] = [23]
// [2 4] [6] [2*5 + 4*6] [34]
Assert.AreEqual(23, result.x);
Assert.AreEqual(34, result.y);
}
[TestMethod]
public void TestTranspose()
{
var matrix = new int2x2(
new int2(1, 2),
new int2(3, 4)
);
var transposed = math.transpose(matrix);
// [1 3] -> [1 2]
// [2 4] [3 4]
Assert.AreEqual(1, transposed.c0.x);
Assert.AreEqual(3, transposed.c0.y);
Assert.AreEqual(2, transposed.c1.x);
Assert.AreEqual(4, transposed.c1.y);
}
[TestMethod]
public void TestDeterminant()
{
var matrix = new int2x2(
new int2(1, 2),
new int2(3, 4)
);
var det = math.determinant(matrix);
// det = 1*4 - 2*3 = 4 - 6 = -2
Assert.AreEqual(-2, det);
}
[TestMethod]
public void TestIdentityMatrix()
{
var identity = int2x2.identity;
var testMatrix = new int2x2(
new int2(5, 6),
new int2(7, 8)
);
var result = math.mul(identity, testMatrix);
Assert.AreEqual(testMatrix.c0.x, result.c0.x);
Assert.AreEqual(testMatrix.c0.y, result.c0.y);
Assert.AreEqual(testMatrix.c1.x, result.c1.x);
Assert.AreEqual(testMatrix.c1.y, result.c1.y);
}
[TestMethod]
public void TestIndexer()
{
var matrix = new int2x2(
new int2(1, 2),
new int2(3, 4)
);
Assert.AreEqual(1, matrix[0].x);
Assert.AreEqual(2, matrix[0].y);
Assert.AreEqual(3, matrix[1].x);
Assert.AreEqual(4, matrix[1].y);
}
[TestMethod]
public void TestComparisonOperators()
{
var a = new int2x2(
new int2(1, 2),
new int2(3, 4)
);
var b = new int2x2(
new int2(1, 2),
new int2(3, 4)
);
var c = new int2x2(
new int2(5, 6),
new int2(7, 8)
);
// Test equality
var isEqual = math.all(a.c0 == b.c0) && math.all(a.c1 == b.c1);
Assert.IsTrue(isEqual);
var isDifferent = math.any(a.c0 != c.c0) || math.any(a.c1 != c.c1);
Assert.IsTrue(isDifferent);
}
}

View File

@@ -0,0 +1,167 @@
using Misaki.HighPerformance.Mathematics;
namespace Misaki.HighPerformance.Test.UnitTest.Mathematics;
[TestClass]
public class TestInt3
{
[TestMethod]
public void TestConstructors()
{
// Default constructor
var v1 = new int3();
Assert.AreEqual(0, v1.x);
Assert.AreEqual(0, v1.y);
Assert.AreEqual(0, v1.z);
// Single value constructor
var v2 = new int3(5);
Assert.AreEqual(5, v2.x);
Assert.AreEqual(5, v2.y);
Assert.AreEqual(5, v2.z);
// Component constructor
var v3 = new int3(1, 2, 3);
Assert.AreEqual(1, v3.x);
Assert.AreEqual(2, v3.y);
Assert.AreEqual(3, v3.z);
// Mixed constructors
var v4 = new int3(new int2(1, 2), 3);
Assert.AreEqual(1, v4.x);
Assert.AreEqual(2, v4.y);
Assert.AreEqual(3, v4.z);
var v5 = new int3(1, new int2(2, 3));
Assert.AreEqual(1, v5.x);
Assert.AreEqual(2, v5.y);
Assert.AreEqual(3, v5.z);
}
[TestMethod]
public void TestArithmeticOperators()
{
var a = new int3(10, 20, 30);
var b = new int3(5, 4, 6);
// Addition
var add = a + b;
Assert.AreEqual(15, add.x);
Assert.AreEqual(24, add.y);
Assert.AreEqual(36, add.z);
// Subtraction
var sub = a - b;
Assert.AreEqual(5, sub.x);
Assert.AreEqual(16, sub.y);
Assert.AreEqual(24, sub.z);
// Multiplication
var mul = a * b;
Assert.AreEqual(50, mul.x);
Assert.AreEqual(80, mul.y);
Assert.AreEqual(180, mul.z);
// Division
var div = a / b;
Assert.AreEqual(2, div.x);
Assert.AreEqual(5, div.y);
Assert.AreEqual(5, div.z);
// Scalar operations
var scalarMul = a * 2;
Assert.AreEqual(20, scalarMul.x);
Assert.AreEqual(40, scalarMul.y);
Assert.AreEqual(60, scalarMul.z);
}
[TestMethod]
public void TestBitwiseOperators()
{
var a = new int3(0b1010, 0b1100, 0b1001);
var b = new int3(0b1100, 0b1010, 0b1011);
// Bitwise AND
var and = a & b;
Assert.AreEqual(0b1000, and.x);
Assert.AreEqual(0b1000, and.y);
Assert.AreEqual(0b1001, and.z);
// Bitwise OR
var or = a | b;
Assert.AreEqual(0b1110, or.x);
Assert.AreEqual(0b1110, or.y);
Assert.AreEqual(0b1011, or.z);
}
[TestMethod]
public void TestComparisonOperators()
{
var a = new int3(10, 20, 30);
var b = new int3(10, 20, 30);
var c = new int3(5, 30, 25);
// Equality
Assert.IsTrue(math.all(a == b));
Assert.IsFalse(math.all(a == c));
// Inequality
Assert.IsFalse(math.all(a != b));
Assert.IsTrue(math.all(a != c));
}
[TestMethod]
public void TestSwizzleProperties()
{
var v = new int3(1, 2, 3);
Assert.AreEqual(1, v.x);
Assert.AreEqual(2, v.y);
Assert.AreEqual(3, v.z);
// Test common swizzles if they exist
try
{
var xy = v.xy;
Assert.AreEqual(1, xy.x);
Assert.AreEqual(2, xy.y);
var xyz = v.xyz;
Assert.AreEqual(1, xyz.x);
Assert.AreEqual(2, xyz.y);
Assert.AreEqual(3, xyz.z);
}
catch
{
// Swizzles might not be implemented
}
}
[TestMethod]
public void TestUnaryOperators()
{
var a = new int3(5, -3, 7);
// Unary minus
var neg = -a;
Assert.AreEqual(-5, neg.x);
Assert.AreEqual(3, neg.y);
Assert.AreEqual(-7, neg.z);
// Unary plus
var pos = +a;
Assert.AreEqual(5, pos.x);
Assert.AreEqual(-3, pos.y);
Assert.AreEqual(7, pos.z);
}
[TestMethod]
public void TestIndexer()
{
var v = new int3(10, 20, 30);
Assert.AreEqual(10, v[0]);
Assert.AreEqual(20, v[1]);
Assert.AreEqual(30, v[2]);
}
}

Some files were not shown because too many files have changed in this diff Show More