diff --git a/Misaki.HighPerformance.Jobs/JobExecutionContext.cs b/Misaki.HighPerformance.Jobs/JobExecutionContext.cs
index 1f3943b..9cbe01c 100644
--- a/Misaki.HighPerformance.Jobs/JobExecutionContext.cs
+++ b/Misaki.HighPerformance.Jobs/JobExecutionContext.cs
@@ -4,17 +4,21 @@ public readonly ref struct JobExecutionContext
{
public int ThreadIndex
{
- get;
+ get; init;
}
public IJobScheduler JobScheduler
{
- get;
+ get; init;
}
- public JobExecutionContext(int threadIndex, IJobScheduler jobScheduler)
+ public object? State
{
- ThreadIndex = threadIndex;
- JobScheduler = jobScheduler;
+ get; init;
+ }
+
+ public JobHandle SelfHandle
+ {
+ get; init;
}
}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Jobs/JobInfo.cs b/Misaki.HighPerformance.Jobs/JobInfo.cs
index d236cdb..c371690 100644
--- a/Misaki.HighPerformance.Jobs/JobInfo.cs
+++ b/Misaki.HighPerformance.Jobs/JobInfo.cs
@@ -84,6 +84,9 @@ internal unsafe struct JobInfo
public const int MAX_DEPENDENTS = 8;
+ public void* pJobData;
+ public JobExecutionFunc pExecutionFunc;
+
// The list of jobs that are waiting for THIS job to complete.
public fixed int dependentsID[MAX_DEPENDENTS]; // The actual list of IDs
public fixed int dependentsGeneration[MAX_DEPENDENTS]; // The actual list of generations
@@ -93,9 +96,6 @@ internal unsafe struct JobInfo
public JobRanges jobRanges;
- public void* pJobData;
- public JobExecutionFunc pExecutionFunc;
-
public int state;
public int remainingBatches;
diff --git a/Misaki.HighPerformance.Jobs/JobScheduler.cs b/Misaki.HighPerformance.Jobs/JobScheduler.cs
index fc7493c..389a382 100644
--- a/Misaki.HighPerformance.Jobs/JobScheduler.cs
+++ b/Misaki.HighPerformance.Jobs/JobScheduler.cs
@@ -5,7 +5,6 @@ using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Jobs;
@@ -26,9 +25,12 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
private readonly SemaphoreSlim _workSignal;
private readonly CancellationTokenSource _cts;
+ private readonly object? _state;
+
private bool _disposed = false;
internal bool IsCancellationRequested => _cts.IsCancellationRequested;
+ internal object? State => _state;
public int WorkerCount => _workerThreads.Length;
@@ -37,7 +39,8 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
///
/// The number of worker threads to create. If less than 1, at least one thread will be created.
/// The priority of the worker threads.
- public JobScheduler(int threadCount, ThreadPriority priority = ThreadPriority.Normal)
+ /// The state object for the job scheduler.
+ public JobScheduler(int threadCount, ThreadPriority priority = ThreadPriority.Normal, object? state = null)
{
var workerCount = Math.Max(1, threadCount);
@@ -49,6 +52,8 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
_workSignal = new SemaphoreSlim(0);
_cts = new CancellationTokenSource();
+ _state = state;
+
_workerThreads = new WorkerThread[workerCount];
for (var i = 0; i < workerCount; i++)
@@ -308,6 +313,8 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
}
}
+ info.additionalDependents.Dispose();
+
_freeList.Free(info.pJobData);
_jobInfoPool.Remove(handle.ID, handle.Generation);
}
@@ -320,7 +327,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
{
return JobHandle.Invalid;
}
-
+
*(T*)pJobData = job;
var jobInfo = new JobInfo
@@ -573,10 +580,11 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
{
if (info.pJobData != null)
{
- NativeMemory.Free(info.pJobData);
+ _freeList.Free(info.pJobData);
}
}
+ _freeList.Dispose();
_workSignal.Dispose();
_cts.Dispose();
diff --git a/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj b/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj
index c5b19bc..84987f7 100644
--- a/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj
+++ b/Misaki.HighPerformance.Jobs/Misaki.HighPerformance.Jobs.csproj
@@ -6,7 +6,7 @@
enable
True
True
- 1.6.0
+ 1.6.1
$(AssemblyVersion)
Misaki
https://git.personalnas.com/Misaki/Misaki.HighPerformance.git
diff --git a/Misaki.HighPerformance.Jobs/WorkerThread.cs b/Misaki.HighPerformance.Jobs/WorkerThread.cs
index ef98ab1..4721d03 100644
--- a/Misaki.HighPerformance.Jobs/WorkerThread.cs
+++ b/Misaki.HighPerformance.Jobs/WorkerThread.cs
@@ -106,7 +106,14 @@ internal class WorkerThread : IDisposable
if (jobInfo.pExecutionFunc != null)
{
- var ctx = new JobExecutionContext(_index, _scheduler);
+ var ctx = new JobExecutionContext
+ {
+ ThreadIndex = _index,
+ JobScheduler = _scheduler,
+ State = _scheduler.State,
+ SelfHandle = handle,
+ };
+
if (!jobInfo.pExecutionFunc(jobInfo.pJobData, ref jobInfo.jobRanges, ref jobInfo.remainingBatches, in ctx))
{
// If the job returns false, it means it we are not the last worker to process this job, so we should not mark it as complete yet.
diff --git a/Misaki.HighPerformance.Test/Benchmark/SPMDBenchmark.cs b/Misaki.HighPerformance.Test/Benchmark/SPMDBenchmark.cs
index 7e603c4..72a245a 100644
--- a/Misaki.HighPerformance.Test/Benchmark/SPMDBenchmark.cs
+++ b/Misaki.HighPerformance.Test/Benchmark/SPMDBenchmark.cs
@@ -94,7 +94,6 @@ public unsafe class SPMDBenchmark
height = _SIZE,
};
- var ctx = new JobExecutionContext(-1, _scheduler);
- job.Run(_SIZE * _SIZE, in ctx);
+ job.Run(_SIZE * _SIZE, default);
}
}
diff --git a/Misaki.HighPerformance.Test/Program.cs b/Misaki.HighPerformance.Test/Program.cs
index b276556..a6aa1b7 100644
--- a/Misaki.HighPerformance.Test/Program.cs
+++ b/Misaki.HighPerformance.Test/Program.cs
@@ -1,21 +1,136 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
//BenchmarkRunner.Run();
-//AllocationManager.Initialize(AllocationManagerInitOpts.Default);
-//var set = new UnsafeBitSet(100, AllocationHandle.Persistent, AllocationOption.Clear);
-//set.SetBit(0);
-//Console.WriteLine(set.NextSetBit(0));
+AllocationManager.Initialize(AllocationManagerInitOpts.Default);
+var set = new UnsafeBitSet(100, AllocationHandle.Persistent, AllocationOption.Clear);
+set.SetBit(0);
+Console.WriteLine(set.NextSetBit(0));
-//set.Dispose();
-//AllocationManager.Dispose();
+set.Dispose();
+AllocationManager.Dispose();
-var span = Span.Empty;
-unsafe
-{
- fixed (int* ptr = span)
- {
- Console.WriteLine((IntPtr)ptr);
- }
-}
+//unsafe
+//{
+// var testC = new TestC();
+// testC.BindToVtables();
+// testC.MyValue = 42;
+
+// // 1. Casting to IB (Direct cast, offset 0)
+// var pB = (IB.Interface*)&testC;
+// pB->MethodA();
+// pB->MethodB();
+
+// // 2. Casting to IC (Pointer adjustment needed!)
+// // We must offset the pointer by the size of one void* so it points to lpVtbl_IC
+// var pC = (IC.Interface*)((byte*)&testC + sizeof(void*));
+// pC->MethodC();
+//}
+
+//public interface IA
+//{
+// [StructLayout(LayoutKind.Sequential)]
+// unsafe struct Interface
+// {
+// public void** lpVtbl;
+// public void MethodA() => ((delegate*)lpVtbl[0])((Interface*)Unsafe.AsPointer(ref this));
+// }
+//}
+
+//public interface IB : IA
+//{
+// [StructLayout(LayoutKind.Sequential)]
+// new unsafe struct Interface
+// {
+// public void** lpVtbl;
+// // IA Methods
+// public void MethodA() => ((delegate*)lpVtbl[0])((Interface*)Unsafe.AsPointer(ref this));
+// // IB Methods
+// public void MethodB() => ((delegate*)lpVtbl[1])((Interface*)Unsafe.AsPointer(ref this));
+// }
+//}
+
+//public interface IC
+//{
+// [StructLayout(LayoutKind.Sequential)]
+// unsafe struct Interface
+// {
+// public void** lpVtbl;
+// public void MethodC() => ((delegate*)lpVtbl[0])((Interface*)Unsafe.AsPointer(ref this));
+// }
+//}
+
+//[StructLayout(LayoutKind.Sequential)]
+//public unsafe partial struct TestC
+//{
+// // Offset 0: Primary VTable (Covers IB and IA)
+// public void** lpVtbl_IB;
+
+// // Offset 8 (on 64-bit): Secondary VTable (Covers IC)
+// public void** lpVtbl_IC;
+
+// // Offset 16: Fields
+// public int MyValue;
+//}
+
+//public unsafe partial struct TestC
+//{
+// public struct Vtbl_IB
+// {
+// public delegate* MethodA;
+// public delegate* MethodB;
+// }
+
+// public struct Vtbl_IC
+// {
+// public delegate* MethodC;
+// }
+
+// [FixedAddressValueType]
+// public static readonly Vtbl_IB Table_IB = new Vtbl_IB
+// {
+// MethodA = &Native_MethodA,
+// MethodB = &Native_MethodB
+// };
+
+// [FixedAddressValueType]
+// public static readonly Vtbl_IC Table_IC = new Vtbl_IC
+// {
+// MethodC = &Native_MethodC
+// };
+
+// public void BindToVtables()
+// {
+// fixed (Vtbl_IB* pIB = &Table_IB)
+// lpVtbl_IB = (void**)pIB;
+// fixed (Vtbl_IC* pIC = &Table_IC)
+// lpVtbl_IC = (void**)pIC;
+// }
+
+// // --- IB & IA Implementations ---
+// public static void Native_MethodA(IB.Interface* self)
+// {
+// // For IB/IA, no adjustment is needed because lpVtbl_IB is at offset 0
+// var pThis = (TestC*)self;
+// Console.WriteLine($"MethodA called. MyValue: {pThis->MyValue}");
+// }
+
+// public static void Native_MethodB(IB.Interface* self)
+// {
+// var pThis = (TestC*)self;
+// Console.WriteLine($"MethodB called. MyValue: {pThis->MyValue}");
+// }
+
+// // --- IC Implementations ---
+// public static void Native_MethodC(IC.Interface* self)
+// {
+// // WARNING: 'self' points to the lpVtbl_IC field, NOT the start of TestC!
+// // We must shift the pointer backward by the size of lpVtbl_IB (sizeof(void*))
+// // to reconstruct the original TestC pointer, otherwise we read garbage memory.
+// var pThis = (TestC*)((byte*)self - sizeof(void*));
+// Console.WriteLine($"MethodC called. MyValue: {pThis->MyValue}");
+// }
+//}
\ No newline at end of file
diff --git a/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobSystem.cs b/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobSystem.cs
index d4a645e..4552bd9 100644
--- a/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobSystem.cs
+++ b/Misaki.HighPerformance.Test/UnitTest/Jobs/TestJobSystem.cs
@@ -275,8 +275,7 @@ public unsafe class TestJobSystem
height = size,
};
- var ctx = new JobExecutionContext(-1, s_jobScheduler);
- vectorJob.Run(size * size, in ctx);
+ vectorJob.Run(size * size, default);
var spmdBuf = stackalloc float[size * size];
var ss = new Span(spmdBuf, size * size);