diff --git a/Solver/ActionPool.cs b/Solver/ActionPool.cs index c213ee5..c7682a2 100644 --- a/Solver/ActionPool.cs +++ b/Solver/ActionPool.cs @@ -1,5 +1,6 @@ using Craftimizer.Simulator.Actions; using System.Diagnostics.Contracts; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -39,77 +40,55 @@ public readonly struct ActionPool }); public const int MaskSize = 32; - public const int EnumSize = 37; - private unsafe struct EnumBuffer - { - private fixed byte data[MaskSize]; + // Bitmask of accepted actions + private readonly ulong acceptedActions; - public ActionType this[int index] => (ActionType)data[index]; - - public EnumBuffer(ReadOnlySpan actions) - { - fixed (byte* dataPtr = data) - actions.CopyTo(new Span(dataPtr, MaskSize)); - } - - public readonly ActionType[] ToArray(int size) - { - fixed (byte* dataPtr = data) - return new Span(dataPtr, size).ToArray(); - } - } - - private unsafe struct LUTBuffer - { - private fixed byte data[EnumSize]; - - public byte this[ActionType index] => data[(byte)index]; - - public LUTBuffer(ReadOnlySpan actions) - { - for (var i = 0; i < EnumSize; i++) - data[i] = 0xFF; - for (var i = 0; i < actions.Length; i++) - data[(byte)actions[i]] = (byte)i; - } - } - - // List of accepted actions (max 32) - private readonly EnumBuffer acceptedActions; - // Lookup table for accepted actions (ActionType as idx -> idx in acceptedActions) - private readonly LUTBuffer acceptedActionsLUT; - private readonly byte size; - - internal ActionType[] AcceptedActions => acceptedActions.ToArray(size); + internal ActionType[] AcceptedActions => GetActions(); + internal int Count => BitOperations.PopCount(acceptedActions); public ActionPool(ReadOnlySpan actions) { - if (actions.Length > MaskSize) - throw new ArgumentOutOfRangeException(nameof(actions), actions.Length, $"ActionPool only supports up to {MaskSize} actions"); + acceptedActions = 0; + foreach (var action in actions) + acceptedActions |= 1ul << ((byte)action); - acceptedActions = new(actions); - acceptedActionsLUT = new(actions); - size = (byte)actions.Length; + if (Count > MaskSize) + throw new ArgumentOutOfRangeException(nameof(actions), actions.Length, $"ActionPool only supports up to {MaskSize} actions"); } - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal byte FromAction(ActionType action) + private ActionType[] GetActions() { - var ret = acceptedActionsLUT[action]; - if (ret == 0xFF) - throw new ArgumentOutOfRangeException(nameof(action), action, $"Action {action} is unsupported in this pool."); + var ret = new ActionType[Count]; + var i = 0; + foreach (var v in (byte[])Enum.GetValuesAsUnderlyingType()) + { + if ((acceptedActions & (1ul << v)) != 0) + ret[i++] = (ActionType)v; + } return ret; } [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ActionType ToAction(byte index) + internal int FromAction(ActionType action) { - if (index < 0 || index >= size) + if ((acceptedActions & (1ul << (byte)action)) == 0) + throw new ArgumentOutOfRangeException(nameof(action), action, "Action is not accepted by this pool."); + + // Get number of 1s before action + return BitOperations.PopCount(acceptedActions & ((1ul << (byte)action) - 1)); + } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ActionType ToAction(int index) + { + if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(nameof(index), index, $"Index {index} is out of range for this pool."); - return acceptedActions[index]; + + // Get index of (index+1)th 1 in set + return (ActionType)Intrinsics.NthBitSet(acceptedActions, index); } [Pure] diff --git a/Solver/ActionSet.cs b/Solver/ActionSet.cs index 3a8d92c..989c828 100644 --- a/Solver/ActionSet.cs +++ b/Solver/ActionSet.cs @@ -34,7 +34,7 @@ public struct ActionSet public readonly bool HasAction(in ActionPool pool, ActionType action) => (bits & pool.ToMask(action)) != 0; [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly ActionType ElementAt(in ActionPool pool, int index) => pool.ToAction((byte)(Intrinsics.NthBitSet(bits, index) - 1)); + public readonly ActionType ElementAt(in ActionPool pool, int index) => pool.ToAction(Intrinsics.NthBitSet(bits, index) - 1); [Pure] public readonly int Count => BitOperations.PopCount(bits); diff --git a/Solver/Intrinsics.cs b/Solver/Intrinsics.cs index df946a7..d901f7f 100644 --- a/Solver/Intrinsics.cs +++ b/Solver/Intrinsics.cs @@ -92,11 +92,46 @@ internal static class Intrinsics return _base; } + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int NthBitSetScalar(ulong value, int n) + { + var mask = 0x00000000FFFFFFFFul; + var size = 32; + var _base = 0; + + if (n++ >= BitOperations.PopCount(value)) + return 64; + + while (size > 0) + { + var count = BitOperations.PopCount(value & mask); + if (n > count) + { + _base += size; + size >>= 1; + mask |= mask << size; + } + else + { + size >>= 1; + mask >>= size; + } + } + + return _base; + } + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int NthBitSetBMI2(uint value, int n) => BitOperations.TrailingZeroCount(Bmi2.ParallelBitDeposit(1u << n, value)); + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int NthBitSetBMI2(ulong value, int n) => + BitOperations.TrailingZeroCount(Bmi2.X64.ParallelBitDeposit(1ul << n, value)); + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int NthBitSet(uint value, int n) @@ -109,6 +144,18 @@ internal static class Intrinsics NthBitSetScalar(value, n); } + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int NthBitSet(ulong value, int n) + { + if (n >= BitOperations.PopCount(value)) + return 64; + + return Bmi2.X64.IsSupported ? + NthBitSetBMI2(value, n) : + NthBitSetScalar(value, n); + } + [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector ReciprocalSqrt(Vector data) diff --git a/Test/Solver/ActionSet.cs b/Test/Solver/ActionSet.cs index 46581a7..f2d6e77 100644 --- a/Test/Solver/ActionSet.cs +++ b/Test/Solver/ActionSet.cs @@ -10,16 +10,49 @@ public class ActionSetTests [TestMethod] public void TestActionPoolSize() { - Assert.AreEqual(ActionPool.EnumSize, Enum.GetValues().Length); Assert.AreEqual(ActionPool.MaskSize, Unsafe.SizeOf() * 8); } + [TestMethod] + public void TestActionPoolConstructor() + { + CollectionAssert.AreEquivalent(new ActionType[] + { + ActionType.StandardTouchCombo, + ActionType.AdvancedTouchCombo, + ActionType.FocusedTouchCombo, + ActionType.FocusedSynthesisCombo, + ActionType.TrainedFinesse, + ActionType.PrudentSynthesis, + ActionType.Groundwork, + ActionType.AdvancedTouch, + ActionType.CarefulSynthesis, + ActionType.TrainedEye, + ActionType.DelicateSynthesis, + ActionType.PreparatoryTouch, + ActionType.Reflect, + ActionType.PrudentTouch, + ActionType.Manipulation, + ActionType.MuscleMemory, + ActionType.ByregotsBlessing, + ActionType.WasteNot2, + ActionType.BasicSynthesis, + ActionType.Innovation, + ActionType.GreatStrides, + ActionType.StandardTouch, + ActionType.Veneration, + ActionType.WasteNot, + ActionType.MastersMend, + ActionType.BasicTouch, + }, pool.AcceptedActions); + } + [TestMethod] public void TestAcceptedActions() { foreach (var i in Enum.GetValues()) { - byte idx; + int idx; try { idx = pool.FromAction(i); @@ -101,18 +134,18 @@ public class ActionSetTests Assert.AreEqual(4, set.Count); - Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(in pool, 0)); - Assert.AreEqual(ActionType.Reflect, set.ElementAt(in pool, 1)); - Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(in pool, 2)); - Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(in pool, 3)); + Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(in pool, 0)); + Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(in pool, 1)); + Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(in pool, 2)); + Assert.AreEqual(ActionType.Reflect, set.ElementAt(in pool, 3)); set.RemoveAction(in pool, ActionType.Reflect); Assert.AreEqual(3, set.Count); - Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(in pool, 0)); + Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(in pool, 0)); Assert.AreEqual(ActionType.ByregotsBlessing, set.ElementAt(in pool, 1)); - Assert.AreEqual(ActionType.BasicSynthesis, set.ElementAt(in pool, 2)); + Assert.AreEqual(ActionType.DelicateSynthesis, set.ElementAt(in pool, 2)); } [TestMethod]