diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..c0f35c9e
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,29 @@
+name: CI
+
+on:
+ push:
+ branches: [ master, main ]
+ pull_request:
+
+jobs:
+ build-test:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 8.0.x
+ 10.0.x
+ 11.0.x
+ dotnet-quality: preview
+ - name: Build
+ run: dotnet build ImTools.slnx -c Release -p:DevMode=false
+ - name: Test
+ run: dotnet test --no-build -c Release -p:DevMode=false test/ImTools.UnitTests/ImTools.UnitTests.csproj
+ - name: Pack
+ run: BuildScripts\NugetPack.bat
+ - uses: actions/upload-artifact@v4
+ with:
+ name: packages
+ path: .dist/packages/*.nupkg
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 051e436f..a91c68fc 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,9 @@
{
"cSpell.words": [
+ "Aval",
+ "Bval",
"cref",
+ "Cval",
"Hasher",
"langword",
"mentries",
@@ -11,5 +14,6 @@
],
"files.watcherExclude": {
"**/target": true
- }
+ },
+ "dotnet.defaultSolution": "ImTools.slnx"
}
\ No newline at end of file
diff --git a/ImTools.sln b/ImTools.sln
deleted file mode 100644
index 73d333c3..00000000
--- a/ImTools.sln
+++ /dev/null
@@ -1,107 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29424.173
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImTools", "src\ImTools\ImTools.csproj", "{6B4B1E6B-6C7B-4FE4-8620-D4ABEE67C592}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A6F5B847-FF68-4EA1-98D4-B7517296AC16}"
- ProjectSection(SolutionItems) = preProject
- src\Directory.Build.props = src\Directory.Build.props
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F779D683-7F9C-4B65-95C5-ED8E75642362}"
- ProjectSection(SolutionItems) = preProject
- test\Directory.Build.props = test\Directory.Build.props
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "playground", "playground", "{D109DF34-CB08-42E5-8F8F-CD2D4D4A8DB3}"
- ProjectSection(SolutionItems) = preProject
- playground\Directory.Build.props = playground\Directory.Build.props
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F6D087C9-3B9A-413A-86DD-14BD1F6BF013}"
- ProjectSection(SolutionItems) = preProject
- .editorconfig = .editorconfig
- .travis.yml = .travis.yml
- appveyor.yml = appveyor.yml
- build.bat = build.bat
- build_no_legacy.bat = build_no_legacy.bat
- Directory.Build.props = Directory.Build.props
- README.md = README.md
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImTools.UnitTests", "test\ImTools.UnitTests\ImTools.UnitTests.csproj", "{D95E6E09-2052-4F08-A4D4-C73244720920}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImTools.Benchmarks", "playground\ImTools.Benchmarks\ImTools.Benchmarks.csproj", "{113D7A58-BA48-4C82-9E3C-0862BE1186A5}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{CD913EEB-7AF4-47E3-8B7C-02235E5DCDE4}"
- ProjectSection(SolutionItems) = preProject
- build\NuGetPack.bat = build\NuGetPack.bat
- build\NuGetPublish.bat = build\NuGetPublish.bat
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuspecs", "nuspecs", "{29BC7B76-D817-425F-98F1-A92454D23857}"
- ProjectSection(SolutionItems) = preProject
- nuspecs\ImTools.nuspec = nuspecs\ImTools.nuspec
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImTools.UnionPlayground", "playground\ImTools.UnionPlayground\ImTools.UnionPlayground.csproj", "{EFBD055A-3C8A-45C7-8718-D9C53A66521D}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImTools.SimpleDIPlayground", "playground\ImTools.SimpleDIPlayground\ImTools.SimpleDIPlayground.csproj", "{60B58862-31B3-48DC-BFF3-42A9B0A194A5}"
-EndProject
-Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "ImTools.UnionCsVsFsBenchmarks", "playground\ImTools.UnionCsVsFsBenchmarks\ImTools.UnionCsVsFsBenchmarks.fsproj", "{239B6CA0-2860-4A67-859F-FD4EF6DAA547}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Experiments", "playground\Experiments\Experiments.csproj", "{88D8A585-1936-41DC-9A6E-74589EB6EA97}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {6B4B1E6B-6C7B-4FE4-8620-D4ABEE67C592}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6B4B1E6B-6C7B-4FE4-8620-D4ABEE67C592}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6B4B1E6B-6C7B-4FE4-8620-D4ABEE67C592}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6B4B1E6B-6C7B-4FE4-8620-D4ABEE67C592}.Release|Any CPU.Build.0 = Release|Any CPU
- {D95E6E09-2052-4F08-A4D4-C73244720920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D95E6E09-2052-4F08-A4D4-C73244720920}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D95E6E09-2052-4F08-A4D4-C73244720920}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D95E6E09-2052-4F08-A4D4-C73244720920}.Release|Any CPU.Build.0 = Release|Any CPU
- {113D7A58-BA48-4C82-9E3C-0862BE1186A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {113D7A58-BA48-4C82-9E3C-0862BE1186A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {113D7A58-BA48-4C82-9E3C-0862BE1186A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {113D7A58-BA48-4C82-9E3C-0862BE1186A5}.Release|Any CPU.Build.0 = Release|Any CPU
- {EFBD055A-3C8A-45C7-8718-D9C53A66521D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EFBD055A-3C8A-45C7-8718-D9C53A66521D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {EFBD055A-3C8A-45C7-8718-D9C53A66521D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {EFBD055A-3C8A-45C7-8718-D9C53A66521D}.Release|Any CPU.Build.0 = Release|Any CPU
- {60B58862-31B3-48DC-BFF3-42A9B0A194A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {60B58862-31B3-48DC-BFF3-42A9B0A194A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {60B58862-31B3-48DC-BFF3-42A9B0A194A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {60B58862-31B3-48DC-BFF3-42A9B0A194A5}.Release|Any CPU.Build.0 = Release|Any CPU
- {239B6CA0-2860-4A67-859F-FD4EF6DAA547}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {239B6CA0-2860-4A67-859F-FD4EF6DAA547}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {239B6CA0-2860-4A67-859F-FD4EF6DAA547}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {239B6CA0-2860-4A67-859F-FD4EF6DAA547}.Release|Any CPU.Build.0 = Release|Any CPU
- {88D8A585-1936-41DC-9A6E-74589EB6EA97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {88D8A585-1936-41DC-9A6E-74589EB6EA97}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {88D8A585-1936-41DC-9A6E-74589EB6EA97}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {88D8A585-1936-41DC-9A6E-74589EB6EA97}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {6B4B1E6B-6C7B-4FE4-8620-D4ABEE67C592} = {A6F5B847-FF68-4EA1-98D4-B7517296AC16}
- {D95E6E09-2052-4F08-A4D4-C73244720920} = {F779D683-7F9C-4B65-95C5-ED8E75642362}
- {113D7A58-BA48-4C82-9E3C-0862BE1186A5} = {D109DF34-CB08-42E5-8F8F-CD2D4D4A8DB3}
- {EFBD055A-3C8A-45C7-8718-D9C53A66521D} = {D109DF34-CB08-42E5-8F8F-CD2D4D4A8DB3}
- {60B58862-31B3-48DC-BFF3-42A9B0A194A5} = {D109DF34-CB08-42E5-8F8F-CD2D4D4A8DB3}
- {239B6CA0-2860-4A67-859F-FD4EF6DAA547} = {D109DF34-CB08-42E5-8F8F-CD2D4D4A8DB3}
- {88D8A585-1936-41DC-9A6E-74589EB6EA97} = {D109DF34-CB08-42E5-8F8F-CD2D4D4A8DB3}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {B8B8E41A-6F08-46C3-828D-C74B91428793}
- EndGlobalSection
-EndGlobal
diff --git a/ImTools.slnx b/ImTools.slnx
new file mode 100644
index 00000000..3424ff77
--- /dev/null
+++ b/ImTools.slnx
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 32097dd6..d1b7e5b9 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Initially split from the [DryIoc](https://github.com/dadhi/dryioc), and now used
[](http://opensource.org/licenses/MIT)
-- Windows, Linux, MacOS [](https://ci.appveyor.com/project/MaksimVolkau/imtools/branch/master)
+- Windows, Linux, MacOS [](https://github.com/dadhi/ImTools/actions/workflows/ci.yml)
- Lib package [](https://www.nuget.org/packages/ImTools.dll)
- Code package [](https://www.nuget.org/packages/ImTools)
- Latest release [](https://github.com/dadhi/ImTools/releases/tag/v4.0.0)
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 4d690fac..00000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-image:
- - Visual Studio 2022
- - Ubuntu
- - macOS
-
-version: 5.0.0-preview-01-b{build}
-
-test: off
-
-skip_commits:
- message: /noci .*/
-
-build_script:
- - dotnet test -p:CILinux=true -c:Release test/ImTools.UnitTests/ImTools.UnitTests.csproj
-
-for:
- - matrix:
- only:
- - image: Visual Studio 2022
-
- build_script:
- - build_with_packaging.bat
-
- artifacts:
- - path: .\.dist\packages\*.nupkg
diff --git a/b.bat b/b.bat
index 5b85acb5..5dad065e 100644
--- a/b.bat
+++ b/b.bat
@@ -5,7 +5,7 @@ echo:## Starting: RESTORE and BUILD...
echo:
dotnet clean -v:m
-dotnet build -c:Release -v:m
+dotnet build ImTools.slnx -c:Release -v:m
if %ERRORLEVEL% neq 0 goto :error
echo:
diff --git a/bd.bat b/bd.bat
index 5b85acb5..5ed2022e 100644
--- a/bd.bat
+++ b/bd.bat
@@ -5,7 +5,7 @@ echo:## Starting: RESTORE and BUILD...
echo:
dotnet clean -v:m
-dotnet build -c:Release -v:m
+dotnet build ImTools.slnx -c:Release -v:m -p:DevMode=true
if %ERRORLEVEL% neq 0 goto :error
echo:
@@ -15,7 +15,7 @@ echo:
echo:## Starting: TESTS...
echo:
-dotnet test --no-build -c Release test/ImTools.UnitTests/ImTools.UnitTests.csproj
+dotnet test --no-build -c Release -p:DevMode=true test/ImTools.UnitTests/ImTools.UnitTests.csproj
if %ERRORLEVEL% neq 0 goto :error
echo:
diff --git a/bm.bat b/bm.bat
index f3da08a7..8f9bcc99 100644
--- a/bm.bat
+++ b/bm.bat
@@ -1 +1 @@
-dotnet run -f net8.0 -c Release --project playground\ImTools.Benchmarks
\ No newline at end of file
+dotnet run -f net10.0 -c Release --project playground\ImTools.Benchmarks
\ No newline at end of file
diff --git a/build.bat b/build.bat
index 896c2de9..84b1a802 100644
--- a/build.bat
+++ b/build.bat
@@ -5,7 +5,7 @@ echo:## Starting: RESTORE and BUILD...
echo:
dotnet clean -v:m
-dotnet build -c:Release -v:m -p:DevMode=false
+dotnet build ImTools.slnx -c:Release -v:m -p:DevMode=false
if %ERRORLEVEL% neq 0 goto :error
echo:
diff --git a/build_with_packaging.bat b/build_with_packaging.bat
index 2d44a746..27a2457f 100644
--- a/build_with_packaging.bat
+++ b/build_with_packaging.bat
@@ -5,7 +5,7 @@ echo:## Starting: RESTORE and BUILD...
echo:
dotnet clean -v:m
-dotnet build -c:Release -p:DevMode=false -v:m
+dotnet build ImTools.slnx -c:Release -p:DevMode=false -v:m
if %ERRORLEVEL% neq 0 goto :error
echo:
diff --git a/playground/Experiments/Experiments.csproj b/playground/Experiments/Experiments.csproj
index f78b5eb9..1f2e8fb7 100644
--- a/playground/Experiments/Experiments.csproj
+++ b/playground/Experiments/Experiments.csproj
@@ -1,9 +1,8 @@
- net8.0
- net8.0;net6.0;net472
- net7.0
+ net11.0;net10.0;net8.0
+ net11.0;net10.0;net8.0;net4721701;1702;AD0001;NU1608
diff --git a/playground/Experiments/FEC_ImTools.cs b/playground/Experiments/FEC_ImTools.cs
index 21bd1402..d8b1d8b8 100644
--- a/playground/Experiments/FEC_ImTools.cs
+++ b/playground/Experiments/FEC_ImTools.cs
@@ -1,17 +1,79 @@
+//
+/*
+The MIT License (MIT)
+
+Copyright (c) 2016-2025 Maksim Volkau
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+// ReSharper disable once InconsistentNaming
+#nullable disable
+
+#if DEBUG
+#define VERIFY_MAP
+#endif
+
+#if !NETSTANDARD2_0_OR_GREATER && !NET472
+#define SUPPORTS_UNSAFE
+#define SUPPORTS_CREATE_SPAN
+#endif
+
+#if LIGHT_EXPRESSION
+namespace FastExpressionCompiler.LightExpression.ImTools;
+#else
+namespace FastExpressionCompiler.ImTools;
+#endif
+
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Text;
+using System.Linq;
using System.Runtime.CompilerServices;
-
-#if NET7_0_OR_GREATER
using System.Runtime.InteropServices;
+using System.Diagnostics.CodeAnalysis;
+
+#if NET8_0_OR_GREATER
+using System.Runtime.Intrinsics;
+using System.Numerics;
#endif
-namespace FastExpressionCompiler.ImTools;
+using static SmallMap;
-using static FHashMap;
+/// Helpers and polyfills for the missing things in the old .NET versions
+public static class RefTools
+{
+ /// Polyfill for the missing returning the `ref` in the failed search scenario.
+ /// Note that the result is the `null` even for the struct `T`, so avoid the accessing its members without the check
+ [MethodImpl((MethodImplOptions)256)]
+ public static ref T GetNullRef() =>
+#if SUPPORTS_UNSAFE
+ ref Unsafe.NullRef();
+#else
+ ref _missing;
+ internal static T _missing = default;
+#endif
+}
-/// Wrapper for the array and count
+/// Wrapper for the array of the specific capacity and a separate count less or equal to this capacity
public struct SmallList
{
/// Array of items
@@ -37,13 +99,17 @@ public SmallList(T[] items) : this(items, items.Length) { }
public static class SmallList
{
internal const int ForLoopCopyCount = 4;
- internal const int InitialCapacity = 4;
+ internal const int DefaultInitialCapacity = 4;
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static ref T ThrowIndexOutOfBounds(int index, int count) =>
+ throw new IndexOutOfRangeException($"Index {index} is out of range of count {count} for SmallList<{typeof(T)},..>.");
[MethodImpl((MethodImplOptions)256)]
- internal static void Expand(ref TItem[] items)
+ internal static void Expand(ref T[] items)
{
// `| 1` is for the case when the length is 0
- var newItems = new TItem[(items.Length << 1) | 1]; // have fun to guess the new length, haha ;-P
+ var newItems = new T[(items.Length << 1) | 1]; // have fun to guess the new length, ha-ha ;-P
if (items.Length > ForLoopCopyCount)
Array.Copy(items, newItems, items.Length);
else
@@ -55,7 +121,7 @@ internal static void Expand(ref TItem[] items)
/// Appends the new default item at the end of the items. Assumes that `index lte items.Length`!
/// `items` should be not null
[MethodImpl((MethodImplOptions)256)]
- public static ref TItem AppendDefaultToNotNullItemsAndGetRef(ref TItem[] items, int index)
+ public static ref T AddDefaultToNotNullItemsAndGetRef(ref T[] items, int index)
{
Debug.Assert(index <= items.Length);
if (index == items.Length)
@@ -65,12 +131,12 @@ public static ref TItem AppendDefaultToNotNullItemsAndGetRef(ref TItem[]
/// Appends the new default item at the end of the items. Assumes that `index lte items.Length`, `items` may be null
[MethodImpl((MethodImplOptions)256)]
- public static ref TItem AppendDefaultAndGetRef(ref TItem[] items, int index, int initialCapacity = InitialCapacity)
+ public static ref T AddDefaultAndGetRef(ref T[] items, int index, int initialCapacity = DefaultInitialCapacity)
{
if (items == null)
{
Debug.Assert(index == 0);
- items = new TItem[initialCapacity];
+ items = new T[initialCapacity];
return ref items[index];
}
@@ -82,624 +148,1272 @@ public static ref TItem AppendDefaultAndGetRef(ref TItem[] items, int ind
/// Returns surely present item ref by its index
[MethodImpl((MethodImplOptions)256)]
- public static ref TItem GetSurePresentItemRef(this ref SmallList source, int index) =>
+ public static ref T GetSurePresentItemRef(this ref SmallList source, int index) =>
ref source.Items[index];
+ /// Returns surely present item ref by its index without boundary checks
+ [MethodImpl((MethodImplOptions)256)]
+ public static ref T GetSurePresentRef(this T[] source, int index) =>
+#if SUPPORTS_UNSAFE
+ ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(source), index);
+#else
+ ref source[index];
+#endif
+
+#if NET7_0_OR_GREATER
+ /// Get the item by-ref without bounds check
+ [MethodImpl((MethodImplOptions)256)]
+ public static ref T GetSurePresentItemRef(this ref T source, int index) where T : struct =>
+ ref Unsafe.Add(ref source, index);
+#endif
+
+ /// Get the item without bounds check
+ [MethodImpl((MethodImplOptions)256)]
+#if NET7_0_OR_GREATER
+ internal static T GetSurePresentItem(this ref T source, int index) where T : struct => Unsafe.Add(ref source, index);
+#else
+ internal static T GetSurePresentItem(this T[] source, int index) => source[index];
+#endif
+
// todo: @perf add the not null variant
/// Appends the new default item to the list and returns ref to it for write or read
[MethodImpl((MethodImplOptions)256)]
- public static ref TItem Append(ref this SmallList source, int initialCapacity = InitialCapacity) =>
- ref AppendDefaultAndGetRef(ref source.Items, source.Count++, initialCapacity);
+ public static ref T Add(this ref SmallList source, int initialCapacity = DefaultInitialCapacity) =>
+ ref AddDefaultAndGetRef(ref source.Items, source.Count++, initialCapacity);
- /// Appends the new item to the list
// todo: @perf add the not null variant
+ /// Appends the new item to the list
[MethodImpl((MethodImplOptions)256)]
- public static void Append(ref this SmallList source, in TItem item, int initialCapacity = InitialCapacity) =>
- AppendDefaultAndGetRef(ref source.Items, source.Count++, initialCapacity) = item;
+ public static void Add(this ref SmallList source, in T item, int initialCapacity = DefaultInitialCapacity) =>
+ AddDefaultAndGetRef(ref source.Items, source.Count++, initialCapacity) = item;
/// Looks for the item in the list and return its index if found or -1 for the absent item
[MethodImpl((MethodImplOptions)256)]
- public static int TryGetIndex(this ref SmallList source, TItem it, TEq eq = default)
- where TEq : struct, IEq
+ public static int TryGetIndex(this T[] items, in T it, int startIndex, int count, TEq eq = default,
+ int notFoundResult = -1)
+ where TEq : struct, IEq
{
- var count = source.Count;
- var items = source.Items;
- for (var i = 0; i < count; ++i)
+ Debug.Assert(items != null);
+ for (var i = startIndex; i < count; ++i)
{
ref var di = ref items[i]; // todo: @perf Marshall?
if (eq.Equals(it, di))
return i;
}
- return -1;
+ return notFoundResult;
}
- /// Returns the ref of the found item or appends the item to the end of the list, and returns ref to it
+ /// Looks for the item in the list and return its index if found or -1 for the absent item
[MethodImpl((MethodImplOptions)256)]
- public static int GetIndexOrAppend(this ref SmallList source, in TItem item, TEq eq)
- where TEq : struct, IEq
- {
- var count = source.Count;
- var items = source.Items;
- for (var i = 0; i < count; ++i)
- {
- ref var di = ref items[i]; // todo: @perf Marshall?
- if (eq.Equals(item, di))
- return i;
- }
- source.Append() = item;
- return -1;
- }
+ public static int TryGetIndex(this ref SmallList source, T it, TEq eq = default)
+ where TEq : struct, IEq
+ => source.Items.TryGetIndex(it, 0, source.Count, eq);
- /// Returns surely present item ref by its index
+ /// Returns the index of the found item or appends the item to the end of the list, and returns its index
[MethodImpl((MethodImplOptions)256)]
- public static ref TItem GetSurePresentItemRef(this ref SmallList4 source, int index)
+ public static int GetIndexOrAdd(this ref SmallList source, in T item, TEq eq = default)
+ where TEq : struct, IEq
{
- Debug.Assert(source.Count != 0);
- Debug.Assert(index < source.Count);
- switch (index)
+ var count = source.Count;
+ if (count != 0)
{
- case 0: return ref source._it0;
- case 1: return ref source._it1;
- case 2: return ref source._it2;
- case 3: return ref source._it3;
- default:
- Debug.Assert(source._rest != null, $"Expecting deeper items are already existing on stack at index: {index}");
- return ref source._rest[index - SmallList4.StackItemCount];
+ var index = TryGetIndex(source.Items, in item, 0, count, eq);
+ if (index != -1)
+ return index;
}
+ source.Add() = item;
+ return count;
}
+}
- /// Returns last present item ref, assumes that the list is not empty!
- [MethodImpl((MethodImplOptions)256)]
- public static ref TItem GetLastSurePresentItem(this ref SmallList4 source) =>
- ref source.GetSurePresentItemRef(source._count - 1);
-
- /// Returns the ref to tombstone indicating the missing item.
- [MethodImpl((MethodImplOptions)256)]
- public static ref TItem NotFound(this ref SmallList4 _) => ref SmallList4.Missing;
+#pragma warning disable CS9101 // UnscopedRef goes wrong on Ubuntu
- /// Appends the default item to the end of the list and returns the reference to it.
- [MethodImpl((MethodImplOptions)256)]
- public static ref TItem AppendDefaultAndGetRef(this ref SmallList4 source)
- {
- var index = source._count++;
- switch (index)
- {
- case 0: return ref source._it0;
- case 1: return ref source._it1;
- case 2: return ref source._it2;
- case 3: return ref source._it3;
- default:
- return ref AppendDefaultAndGetRef(ref source._rest, index - SmallList4.StackItemCount, SmallList4.StackItemCount);
- }
- }
+/// Utilities for Stack4, Stack8, etc.
+public static class Stack
+{
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static ref T ThrowIndexOutOfBounds(int index, int capacity) =>
+ throw new IndexOutOfRangeException($"Index {index} is out of range for Stack{capacity}<{typeof(T)},..>.");
+}
- /// Looks for the item in the list and return its index if found or -1 for the absent item
- [MethodImpl((MethodImplOptions)256)]
- public static int TryGetIndex(this ref SmallList4 source, TItem it, TEq eq = default)
- where TEq : struct, IEq
- {
- switch (source._count)
- {
- case 1:
- if (eq.Equals(it, source._it0)) return 0;
- break;
+/// Stack with the Size information to check it at compile time
+public interface IStack : IStack
+ where TSize : struct, ISize
+ where TStack : struct, IStack
+{
+}
- case 2:
- if (eq.Equals(it, source._it0)) return 0;
- if (eq.Equals(it, source._it1)) return 1;
- break;
+// todo: @wip possibly separate introduce IStackCap interface for the StackCap, because nothing in this interface requires the stack.
+/// Abstracts over collection of the items on stack of the fixed Capacity,
+/// to be used as a part of the hybrid data structures which grow from stack to heap
+public interface IStack
+ where TStack : struct, IStack
+{
+ /// Maximum count of items hold on stack. Should be Power of Two
+ int Capacity { get; }
- case 3:
- if (eq.Equals(it, source._it0)) return 0;
- if (eq.Equals(it, source._it1)) return 1;
- if (eq.Equals(it, source._it2)) return 2;
- break;
+ /// Returns the item by ref to read and write the item value,
+ /// but does not check the index bounds comparing to the `this[index]`
+ [UnscopedRef]
+ ref T GetSurePresentRef(int index);
- default:
- if (eq.Equals(it, source._it0)) return 0;
- if (eq.Equals(it, source._it1)) return 1;
- if (eq.Equals(it, source._it2)) return 2;
- if (eq.Equals(it, source._it3)) return 3;
- if (source._rest != null)
- {
- var count = source._count - SmallList4.StackItemCount;
- var items = source._rest;
- for (var i = 0; i < count; ++i)
- {
- ref var di = ref items[i]; // todo: @perf Marshall?
- if (eq.Equals(it, di))
- return i + SmallList4.StackItemCount;
- }
- }
- break;
- }
- return -1;
- }
+ /// Indexer returning the item by ref to read and write the item value
+ [UnscopedRef]
+ ref T this[int index] { get; }
- /// Returns the ref of the found item or appends the item to the end of the list, and returns ref to it
- [MethodImpl((MethodImplOptions)256)]
- public static int GetIndexOrAppend(this ref SmallList4 source, in TItem item, TEq eq)
- where TEq : struct, IEq
- {
- switch (source._count)
- {
- case 0:
- source._count = 1;
- source._it0 = item;
- return -1;
+#if SUPPORTS_CREATE_SPAN
+ /// Creates a span over the stack items
+ public Span AsSpan();
+#endif
+}
- case 1:
- if (eq.Equals(item, source._it0)) return 0;
- source._count = 2;
- source._it1 = item;
- return -1;
+/// Base marker for collection or container holding some number of items
+public interface ISize
+{
+ /// Returns the size of the collection or container
+ int Size { get; }
+}
+/// Marker for collection or container holding 2 or items
+public interface ISize2Plus : ISize { }
+/// Marker for collection or container holding 4 or more items
+public interface ISize4Plus : ISize2Plus { }
+/// Marker for collection or container holding 8 or more items
+public interface ISize8Plus : ISize4Plus { }
+/// Marker for collection or container holding 16 or more items
+public interface ISize16Plus : ISize8Plus { }
+
+/// Marker for collection or container holding 0 items
+public struct Size0 : ISize
+{
+ ///
+ public int Size => 0;
+}
- case 2:
- if (eq.Equals(item, source._it0)) return 0;
- if (eq.Equals(item, source._it1)) return 1;
- source._count = 3;
- source._it2 = item;
- return -1;
+/// Marker for collection or container holding 4 items
+public struct Size2 : ISize2Plus
+{
+ ///
+ public int Size => 2;
+}
+/// Marker for collection or container holding 4 items
+public struct Size4 : ISize4Plus
+{
+ ///
+ public int Size => 4;
+}
+/// Marker for collection or container holding 8 items
+public struct Size8 : ISize8Plus
+{
+ ///
+ public int Size => 8;
+}
+/// Marker for collection or container holding 16 items
+public struct Size16 : ISize16Plus
+{
+ ///
+ public int Size => 16;
+}
- case 3:
- if (eq.Equals(item, source._it0)) return 0;
- if (eq.Equals(item, source._it1)) return 1;
- if (eq.Equals(item, source._it2)) return 2;
- source._count = 4;
- source._it3 = item;
- return -1;
+/// Implementation of `IStack` for 2 items on stack
+[StructLayout(LayoutKind.Sequential, Pack = 1)]
+public struct Stack2 : IStack>
+{
+ ///
+ public int Capacity => 2;
- default:
- if (eq.Equals(item, source._it0)) return 0;
- if (eq.Equals(item, source._it1)) return 1;
- if (eq.Equals(item, source._it2)) return 2;
- if (eq.Equals(item, source._it3)) return 3;
+ internal T _it0, _it1;
- var restCount = source._count - SmallList4.StackItemCount;
- if (restCount != 0)
- {
- var items = source._rest;
- for (var i = 0; i < restCount; ++i)
- {
- ref var di = ref items[i]; // todo: @perf Marshall?
- if (eq.Equals(item, di))
- return i + SmallList4.StackItemCount;
- }
- }
- AppendDefaultAndGetRef(ref source._rest, restCount, SmallList4.StackItemCount) = item;
- ++source._count;
- return -1;
+ ///
+ [UnscopedRef]
+ [MethodImpl((MethodImplOptions)256)]
+ public ref T GetSurePresentRef(int index)
+ {
+#if SUPPORTS_UNSAFE
+ return ref Unsafe.Add(ref _it0, index);
+#else
+ switch (index)
+ {
+ case 0: return ref _it0;
+ default: return ref _it1;
}
+#endif
}
- /// Returns surely present item ref by its index
- [MethodImpl((MethodImplOptions)256)]
- public static ref TItem GetSurePresentItemRef(this ref SmallList2 source, int index)
+ ///
+ [UnscopedRef]
+ public ref T this[int index]
{
- Debug.Assert(source.Count != 0);
- Debug.Assert(index < source.Count);
- switch (index)
+ [MethodImpl((MethodImplOptions)256)]
+ get
{
- case 0: return ref source._it0;
- case 1: return ref source._it1;
- default:
- Debug.Assert(source._rest != null, $"Expecting deeper items are already existing on stack at index: {index}");
- return ref source._rest[index - SmallList2.StackItemCount];
+ if (index >= 0 & index < Capacity)
+ return ref GetSurePresentRef(index);
+ return ref Stack.ThrowIndexOutOfBounds(index, Capacity);
}
}
- /// Returns last present item ref, assumes that the list is not empty!
+#if SUPPORTS_CREATE_SPAN
+ ///
[MethodImpl((MethodImplOptions)256)]
- public static ref TItem GetLastSurePresentItem(this ref SmallList2 source) =>
- ref source.GetSurePresentItemRef(source._count - 1);
+ public Span AsSpan() => MemoryMarshal.CreateSpan(ref _it0, Capacity);
+#endif
+}
- /// Returns the ref to tombstone indicating the missing item.
- [MethodImpl((MethodImplOptions)256)]
- public static ref TItem NotFound(this ref SmallList2 _) => ref SmallList2.Missing;
+/// Implementation of `IStack` for 4 items on stack
+[StructLayout(LayoutKind.Sequential, Pack = 1)]
+public struct Stack4 : IStack>
+{
+ ///
+ public int Capacity => 4;
- /// Appends the default item to the end of the list and returns the reference to it.
+ internal T _it0, _it1, _it2, _it3;
+
+ ///
+ [UnscopedRef]
[MethodImpl((MethodImplOptions)256)]
- public static ref TItem AppendDefaultAndGetRef(this ref SmallList2 source)
+ public ref T GetSurePresentRef(int index)
{
- var index = source._count++;
+#if SUPPORTS_UNSAFE
+ return ref Unsafe.Add(ref _it0, index);
+#else
switch (index)
{
- case 0: return ref source._it0;
- case 1: return ref source._it1;
- default:
- return ref AppendDefaultAndGetRef(ref source._rest, index - SmallList2.StackItemCount, SmallList2.StackItemCount);
+ case 0: return ref _it0;
+ case 1: return ref _it1;
+ case 2: return ref _it2;
+ default: return ref _it3;
}
+#endif
}
- /// Looks for the item in the list and return its index if found or -1 for the absent item
- [MethodImpl((MethodImplOptions)256)]
- public static int TryGetIndex(this ref SmallList2 source, TItem it, TEq eq = default)
- where TEq : struct, IEq
+ ///
+ [UnscopedRef]
+ public ref T this[int index]
{
- switch (source._count)
+ [MethodImpl((MethodImplOptions)256)]
+ get
{
- case 1:
- if (eq.Equals(it, source._it0)) return 0;
- break;
-
- default:
- if (eq.Equals(it, source._it0)) return 0;
- if (eq.Equals(it, source._it1)) return 1;
- if (source._rest != null)
- {
- var count = source._count - SmallList2.StackItemCount;
- var items = source._rest;
- for (var i = 0; i < count; ++i)
- {
- ref var di = ref items[i]; // todo: @perf Marshall?
- if (eq.Equals(it, di))
- return i + SmallList2.StackItemCount;
- }
- }
- break;
+ if (index >= 0 & index < Capacity)
+ return ref GetSurePresentRef(index);
+ return ref Stack.ThrowIndexOutOfBounds(index, Capacity);
}
- return -1;
}
- /// Returns the ref of the found item or appends the item to the end of the list, and returns ref to it
+#if SUPPORTS_CREATE_SPAN
+ ///
[MethodImpl((MethodImplOptions)256)]
- public static int GetIndexOrAppend(this ref SmallList2 source, TItem item, TEq eq)
- where TEq : struct, IEq
- {
- switch (source._count)
- {
- case 0:
- source._count = 1;
- source._it0 = item;
- return -1;
-
- case 1:
- if (eq.Equals(item, source._it0)) return 0;
- source._count = 2;
- source._it1 = item;
- return -1;
-
- default:
- if (eq.Equals(item, source._it0)) return 0;
- if (eq.Equals(item, source._it1)) return 1;
-
- var restCount = source._count - SmallList2.StackItemCount;
- if (restCount != 0)
- {
- var items = source._rest;
- for (var i = 0; i < restCount; ++i)
- {
- ref var di = ref items[i]; // todo: @perf Marshall?
- if (eq.Equals(item, di))
- return i + SmallList2.StackItemCount;
- }
- }
- AppendDefaultAndGetRef(ref source._rest, restCount, SmallList2.StackItemCount) = item;
- ++source._count;
- return -1;
- }
- }
+ public Span AsSpan() => MemoryMarshal.CreateSpan(ref _it0, Capacity);
+#endif
}
-/// List with the number of first items (4) stored inside its struct and the rest in the growable array.
-/// Supports addition and removal (removel is without resize) only at the end of the list, aka Stack behavior
-public struct SmallList4
+/// Implementation of `IStack` for 8 items on stack
+[StructLayout(LayoutKind.Sequential, Pack = 1)]
+public struct Stack8 : IStack>
{
- /// The number of entries stored inside the map itself without moving them to array on heap
- public const int StackItemCount = 4;
-
- // todo: @check what if someone stores something in it, it would be a memory leak, but isn't it the same as using `out var` in the returning`false` Try...methods?
- internal static TItem Missing; // return the ref to Tombstone when nothing found
+ ///
+ public int Capacity => 8;
- internal int _count;
- internal TItem _it0, _it1, _it2, _it3;
- internal TItem[] _rest;
+ internal T _it0, _it1, _it2, _it3, _it4, _it5, _it6, _it7;
- /// Gets the number of items in the list
- public int Count
+ ///
+ [UnscopedRef]
+ [MethodImpl((MethodImplOptions)256)]
+ public ref T GetSurePresentRef(int index)
{
- [MethodImpl((MethodImplOptions)256)]
- get => _count;
+#if SUPPORTS_UNSAFE
+ return ref Unsafe.Add(ref _it0, index);
+#else
+ switch (index)
+ {
+ case 0: return ref _it0;
+ case 1: return ref _it1;
+ case 2: return ref _it2;
+ case 3: return ref _it3;
+ case 4: return ref _it4;
+ case 5: return ref _it5;
+ case 6: return ref _it6;
+ default: return ref _it7;
+ }
+#endif
}
- /// Returns surely present item by its index
- public TItem this[int index]
+ ///
+ [UnscopedRef]
+ public ref T this[int index]
{
[MethodImpl((MethodImplOptions)256)]
get
{
- Debug.Assert(_count != 0);
- Debug.Assert(index < _count);
- switch (index)
- {
- case 0: return _it0;
- case 1: return _it1;
- case 2: return _it2;
- case 3: return _it3;
- default:
- Debug.Assert(_rest != null, $"Expecting deeper items are already existing on stack at index: {index}");
- return _rest[index - StackItemCount];
- }
+ if (index >= 0 & index < Capacity)
+ return ref GetSurePresentRef(index);
+ return ref Stack.ThrowIndexOutOfBounds(index, Capacity);
}
}
- /// Adds the item to the end of the list aka the Stack.Push
+#if SUPPORTS_CREATE_SPAN
+ ///
[MethodImpl((MethodImplOptions)256)]
- public void Append(in TItem item)
- {
- var index = _count++;
- switch (index)
- {
- case 0: _it0 = item; break;
- case 1: _it1 = item; break;
- case 2: _it2 = item; break;
- case 3: _it3 = item; break;
- default:
- SmallList.AppendDefaultAndGetRef(ref _rest, index - StackItemCount, StackItemCount) = item;
- break;
- }
- }
+ public Span AsSpan() => MemoryMarshal.CreateSpan(ref _it0, Capacity);
+#endif
+}
- /// Bridge to go from the List.Add
- [MethodImpl((MethodImplOptions)256)]
- public void Add(in TItem item) => Append(in item);
+/// Implementation of `IStack` for 16 items on stack
+[StructLayout(LayoutKind.Sequential, Pack = 1)]
+public struct Stack16 : IStack>
+{
+ ///
+ public int Capacity => 16;
- /// Adds the default item to the end of the list aka the Stack.Push default
+ internal T _it0, _it1, _it2, _it3, _it4, _it5, _it6, _it7;
+ internal T _it8, _it9, _it10, _it11, _it12, _it13, _it14, _it15;
+
+ ///
+ [UnscopedRef]
[MethodImpl((MethodImplOptions)256)]
- public void AppendDefault()
+ public ref T GetSurePresentRef(int index)
{
- if (++_count >= StackItemCount)
- SmallList.AppendDefaultAndGetRef(ref _rest, _count - StackItemCount, StackItemCount);
+#if SUPPORTS_UNSAFE
+ return ref Unsafe.Add(ref _it0, index);
+#else
+ switch (index)
+ {
+ case 0: return ref _it0;
+ case 1: return ref _it1;
+ case 2: return ref _it2;
+ case 3: return ref _it3;
+ case 4: return ref _it4;
+ case 5: return ref _it5;
+ case 6: return ref _it6;
+ case 7: return ref _it7;
+ case 8: return ref _it8;
+ case 9: return ref _it9;
+ case 10: return ref _it10;
+ case 11: return ref _it11;
+ case 12: return ref _it12;
+ case 13: return ref _it13;
+ case 14: return ref _it14;
+ default: return ref _it15;
+ }
+#endif
}
- /// Removes the last item from the list aka the Stack Pop. Assumes that the list is not empty!
- [MethodImpl((MethodImplOptions)256)]
- public void RemoveLastSurePresentItem()
+ ///
+ [UnscopedRef]
+ public ref T this[int index]
{
- Debug.Assert(Count != 0);
- var index = --_count;
- switch (index)
+ [MethodImpl((MethodImplOptions)256)]
+ get
{
- case 0: _it0 = default; break;
- case 1: _it1 = default; break;
- case 2: _it2 = default; break;
- case 3: _it3 = default; break;
- default:
- Debug.Assert(_rest != null, $"Expecting a deeper parent stack created before accessing it here at level {index}");
- _rest[index - StackItemCount] = default;
- break;
+ if (index >= 0 & index < Capacity)
+ return ref GetSurePresentRef(index);
+ return ref Stack.ThrowIndexOutOfBounds(index, Capacity);
}
}
+
+#if SUPPORTS_CREATE_SPAN
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public Span AsSpan() => MemoryMarshal.CreateSpan(ref _it0, Capacity);
+#endif
}
-/// List with the number of first items (2) stored inside its struct and the rest in the growable array.
-/// Supports addition and removal (removel is without resize) only at the end of the list, aka Stack behavior
-public struct SmallList2
+/// Generic version of SmallList abstracted for how much items are on the stack
+public struct SmallList : IEnumerable
+ where TStack : struct, IStack
{
- /// The number of entries stored inside the map itself without moving them to array on heap
- public const int StackItemCount = 2;
+ /// Let's enable access to the Count, so you can Pop the item by --list.Count. Just don't forget to nullify the popped item if needed
+ public int Count;
- // todo: @check what if someone stores something in it, it would be a memory leak, but isn't it the same as using `out var` in the returning`false` Try...methods?
- internal static TItem Missing; // return the ref to Tombstone when nothing found
+#pragma warning disable CS0649 // it is fine `Stack` is never assigned to, and will always have its default value
+ /// Let's enable access to the stack, just know what's you doing
+ public TStack Stack;
+#pragma warning restore CS0649
- internal int _count;
- internal TItem _it0, _it1;
- internal TItem[] _rest;
+ /// Exposes the rest on the heap
+ public T[] Rest;
- /// Good stuff
+ /// Ensures that the list has allocated space to hold `count` of items
[MethodImpl((MethodImplOptions)256)]
public void InitCount(int count)
{
- _count = count;
- if (count > StackItemCount)
- _rest = new TItem[count - StackItemCount];
+ Debug.Assert(count > 0, "Count should be more than 0");
+ Debug.Assert(Count == 0, "Initial the count should be 0");
+
+ // Add the StackCapacity empty space at the end, we may use it later for BuildToArray.
+ // The actual source Capacity will be StackCapacity + count.
+ if (count > Stack.Capacity)
+ Rest = new T[count];
+ Count = count;
}
- /// Good stiff
- [MethodImpl((MethodImplOptions)256)]
- public void Init(TItem it0)
+ /// Returns surely present item by ref
+ [UnscopedRef]
+ public ref T this[int index]
{
- _count = 1;
- _it0 = it0;
+ [MethodImpl((MethodImplOptions)256)]
+ get
+ {
+ if (index < 0 | index >= Count)
+ return ref SmallList.ThrowIndexOutOfBounds(index, Count);
+
+ var stackCap = Stack.Capacity;
+ if (index < stackCap)
+ return ref Stack.GetSurePresentRef(index);
+
+ Debug.Assert(Rest != null, "Expecting deeper items are already existing on heap");
+ return ref Rest.GetSurePresentRef(index - stackCap);
+ }
}
- /// Good steff
+ /// Returns a surely present item ref by its index
+ [UnscopedRef]
[MethodImpl((MethodImplOptions)256)]
- public void Init(TItem it0, TItem it1)
+ public ref T GetSurePresentRef(int index)
{
- _count = 2;
- _it0 = it0;
- _it1 = it1;
+ Debug.Assert(Count != 0, "SmallList.GetSurePresentItemRef: list should not be empty");
+ Debug.Assert(index >= 0 & index < Count, $"SmallList.GetSurePresentItemRef: index {index} should be less than Count {Count}");
+
+ var stackCap = Stack.Capacity;
+ if (index < stackCap)
+ return ref Stack.GetSurePresentRef(index);
+
+ Debug.Assert(Rest != null);
+ return ref Rest[index - stackCap];
}
- /// Good staff
+ /// Appends the default item to the end of the list and returns the reference to it.
+ [UnscopedRef]
[MethodImpl((MethodImplOptions)256)]
- public void Init(TItem it0, TItem it1, params TItem[] rest)
+ public ref T AddDefaultAndGetRef()
{
- _count = 2 + rest.Length;
- _it0 = it0;
- _it1 = it1;
- _rest = rest;
+ var index = Count++;
+ var stackCap = Stack.Capacity;
+ if (index < stackCap)
+ return ref Stack.GetSurePresentRef(index);
+ return ref SmallList.AddDefaultAndGetRef(ref Rest, index - stackCap);
}
- /// Good styff
- public void Init(params TItem[] items)
+ /// Adds the item to the end of the list aka the Stack.Push. Returns the index of the added item.
+ [MethodImpl((MethodImplOptions)256)]
+ public int Add(in T item)
{
- switch (items.Length)
- {
- case 0:
- break;
- case 1:
- Init(items[0]);
- break;
- case 2:
- Init(items[0], items[1]);
- break;
- default:
- _count = items.Length;
- _it0 = items[0];
- _it1 = items[1];
- for (var i = StackItemCount; i < items.Length; ++i)
- items[i - StackItemCount] = items[i];
- _rest = items;
- break;
- }
+ var index = Count++;
+ var stackCap = Stack.Capacity;
+ if (index < stackCap)
+ Stack.GetSurePresentRef(index) = item;
+ else
+ SmallList.AddDefaultAndGetRef(ref Rest, index - stackCap) = item;
+ return index;
}
- /// Good staff
- public void Init(TList items) where TList : IReadOnlyList
+ /// Looks for the item in the list and return its index if found or -1 for the absent item
+ [MethodImpl((MethodImplOptions)256)]
+ public int TryGetIndex(in T item, TEq eq = default) where TEq : struct, IEq
{
- if (items is TItem[] arr)
+ if (Count != 0)
{
- Init(arr);
- return;
- }
- switch (items.Count)
- {
- case 0:
- break;
- case 1:
- Init(items[0]);
- break;
- case 2:
- Init(items[0], items[1]);
- break;
- default:
- var count = items.Count;
- var rest = new TItem[count - StackItemCount];
- _count = count;
- _it0 = items[0];
- _it1 = items[1];
- for (var i = StackItemCount; i < count; ++i)
- rest[i - StackItemCount] = items[i];
- _rest = rest;
- break;
+ var index = 0;
+ foreach (var it in this)
+ {
+ if (eq.Equals(item, it))
+ return index;
+ ++index;
+ }
}
+ return -1;
}
- /// Gets the number of items in the list
- public int Count
+ /// Returns the index of the found item or appends the item to the end of the list, and returns its index
+ [MethodImpl((MethodImplOptions)256)]
+ public int GetIndexOrAdd(in T item, TEq eq = default) where TEq : struct, IEq
{
- [MethodImpl((MethodImplOptions)256)]
- get => _count;
+ var i = TryGetIndex(in item, eq);
+ return i != -1 ? i : Add(in item);
}
- /// Returns surely present item by its index
- public TItem this[int index]
+ ///Clears the list, but keeps the already allocated array on heap to reuse in the future
+ [MethodImpl((MethodImplOptions)256)]
+ public void Clear()
{
- [MethodImpl((MethodImplOptions)256)]
- get
+ Stack = default; // todo: @perf is there way to faster clear items on stack?
+ var restCount = Count - Stack.Capacity;
+ if (restCount > 0)
{
- Debug.Assert(_count != 0);
- Debug.Assert(index < _count);
- switch (index)
- {
- case 0: return _it0;
- case 1: return _it1;
- default:
- Debug.Assert(_rest != null, $"Expecting deeper items are already existing on stack at index: {index}");
- return _rest[index - StackItemCount];
- }
+ Debug.Assert(Rest != null, "Expecting deeper items are already existing on heap");
+ Array.Clear(Rest, 0, restCount);
}
+ Count = 0;
}
- /// Adds the item to the end of the list aka the Stack.Push
+ /// Returns last present item ref, assumes that the list is not empty!
+ [UnscopedRef]
[MethodImpl((MethodImplOptions)256)]
- public void Append(in TItem item)
+ public ref T GetLastSurePresentItem()
{
- var index = _count++;
- switch (index)
- {
- case 0: _it0 = item; break;
- case 1: _it1 = item; break;
- default:
- SmallList.AppendDefaultAndGetRef(ref _rest, index - StackItemCount, StackItemCount) = item;
- break;
- }
+ Debug.Assert(Count != 0, "Expecting that the list is not empty");
+ return ref GetSurePresentRef(Count - 1);
}
- /// Bridge to go from the List.Add
+ /// Removes the last item from the list aka the Stack Pop. Assumes that the list is not empty!
[MethodImpl((MethodImplOptions)256)]
- public void Add(in TItem item) => Append(in item);
+ public void RemoveLastSurePresentItem()
+ {
+ Debug.Assert(Count != 0, "SmallList.RemoveLastSurePresentItem: Expecting that the list is not empty");
+ GetSurePresentRef(Count - 1) = default;
+ --Count;
+ }
- /// Adds the default item to the end of the list aka the Stack.Push default
+ /// Returns an enumerator struct
[MethodImpl((MethodImplOptions)256)]
- public void AppendDefault()
+ public SmallListEnumerator GetEnumerator() => new SmallListEnumerator(this);
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+}
+
+/// Enumerator on stack, without allocations
+public struct SmallListEnumerator : IEnumerator, IEnumerator
+ where TStack : struct, IStack
+{
+ private readonly SmallList _list;
+ private int _index;
+ internal SmallListEnumerator(SmallList list)
{
- if (++_count >= StackItemCount)
- SmallList.AppendDefaultAndGetRef(ref _rest, _count - StackItemCount, StackItemCount);
+ _list = list;
+ _index = -1;
}
-
- /// Removes the last item from the list aka the Stack Pop. Assumes that the list is not empty!
+ private T _current;
+ ///
+ public T Current => _current;
+ object IEnumerator.Current => _current;
+ ///
[MethodImpl((MethodImplOptions)256)]
- public void RemoveLastSurePresentItem()
+ public bool MoveNext()
{
- Debug.Assert(Count != 0);
- var index = --_count;
- switch (index)
+ var index = ++_index;
+ if (index < _list.Count)
{
- case 0: _it0 = default; break;
- case 1: _it1 = default; break;
- default:
- Debug.Assert(_rest != null, $"Expecting a deeper parent stack created before accessing it here at level {index}");
- _rest[index - StackItemCount] = default;
- break;
+ _current = index < _list.Stack.Capacity
+ ? _list.Stack.GetSurePresentRef(index)
+ : _list.Rest[index - _list.Stack.Capacity];
+ return true;
}
+ return false;
}
+ ///
+ public void Reset() => _index = -1;
+ ///
+ public void Dispose() { }
+}
- /// Copy items to new the array
+internal static class BitOps
+{
+ /// Gets the next power of two greater than or equal to the specified capacity.
[MethodImpl((MethodImplOptions)256)]
- public TItem[] CopyToArray()
+ public static uint NextPowerOfTwo(uint value)
{
- switch (Count)
- {
- case 0: return Array.Empty();
- case 1: return new[] { _it0 };
- case 2: return new[] { _it0, _it1 };
- default:
- var items = new TItem[Count];
- items[0] = _it0;
- items[1] = _it1;
- Array.Copy(_rest, 0, items, 2, Count - StackItemCount);
- return items;
- }
+#if NET8_0_OR_GREATER
+ return BitOperations.RoundUpToPowerOf2(value);
+#else
+ --value;
+ value |= value >> 1;
+ value |= value >> 2;
+ value |= value >> 4;
+ value |= value >> 8;
+ value |= value >> 16;
+ return value + 1;
+#endif
}
- /// Exposing as list
+ /// Counts the number of leading zero bits in the specified value.
+ /// Useful to get Log2(value) and the number of bits needed to represent the value.
[MethodImpl((MethodImplOptions)256)]
- public IList AsList() => CopyToArray();
+ public static byte LeadingZeroCount(uint value)
+ {
+#if NET8_0_OR_GREATER
+ return (byte)BitOperations.LeadingZeroCount(value);
+#else
+ if (value == 0) return 32;
+ var count = 0;
+ if (value <= 0x0000FFFF) { count += 16; value <<= 16; }
+ if (value <= 0x00FFFFFF) { count += 8; value <<= 8; }
+ if (value <= 0x0FFFFFFF) { count += 4; value <<= 4; }
+ if (value <= 0x3FFFFFFF) { count += 2; value <<= 2; }
+ if (value <= 0x7FFFFFFF) { count += 1; }
+ return (byte)count;
+#endif
+ }
}
-/// Configiration and the tools for the FHashMap map data structure
-public static class FHashMap
+public interface ICapIndex
{
- /// 2^32 / phi for the Fibonacci hashing, where phi is the golden ratio ~1.61803
- public const uint GoldenRatio32 = 2654435769;
+ /// The capacity
+ public int Capacity { get; }
- internal const byte MinFreeCapacityShift = 3; // e.g. for the capacity 16: 16 >> 3 => 2, 12.5% of the free hash slots (it does not mean the entries free slot)
- internal const byte MinHashesCapacityBitShift = 4; // 1 << 3 == 8
+ /// Returns the item by ref to read and write the item value,
+ /// but does not check the index bounds comparing to the `this[index]`
+ [UnscopedRef]
+ ref T GetSurePresentRef(int index);
+}
- /// Upper hash bits spent on storing the probes, e.g. 5 bits mean 31 probes max.
- public const byte MaxProbeBits = 5;
- internal const byte MaxProbeCount = (1 << MaxProbeBits) - 1;
- internal const byte ProbeCountShift = 32 - MaxProbeBits;
- internal const int HashAndIndexMask = ~(MaxProbeCount << ProbeCountShift);
+/// Abstraction to configure your own entries data structure. Check the derived types for the examples
+public interface IGrowingArray : ICapIndex
+{
+ /// Initializes the entries storage to the specified capacity
+ void Init(uint capacity);
- /// The number of entries stored inside the map itself without moving them to array on heap
- public const int StackEntriesCount = 4;
+ /// Initializes the entries storage to the specified capacity without checking the capacity.
+ void InitDoNotCheckCapacity(uint capacityPowerOfTwo);
- /// Creates the map with the storage
- [MethodImpl((MethodImplOptions)256)]
- public static FHashMap> New(byte capacityBitShift = 0)
- where TEq : struct, IEq => new(capacityBitShift);
+ /// Doubles the current capacity of the array preserving the existing elements and count.
+ void DoubleCapacity();
- /// Holds a single entry consisting of key and value.
- /// Value may be set or changed but the key is set in stone (by construction).
- [DebuggerDisplay("{Key?.ToString()}->{Value}")]
- public struct Entry
+ /// Doubles the capacity until the specified value is inside
+ void GrowCapacity(uint capacity);
+}
+
+/// Stores the entries in a single dynamically reallocated growing array
+public struct SingleBackingArray : IGrowingArray
+{
+ internal int _capacityPowerOfTwo;
+ internal T[] _entries;
+
+ ///
+ public int Capacity => _capacityPowerOfTwo;
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public void Init(uint capacity)
+ {
+ _capacityPowerOfTwo = capacity == 0 ? 1 : (int)BitOps.NextPowerOfTwo(capacity);
+ _entries = new T[_capacityPowerOfTwo];
+ }
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public void InitDoNotCheckCapacity(uint capacityPowerOfTwo)
+ {
+ _capacityPowerOfTwo = (int)capacityPowerOfTwo;
+ _entries = new T[capacityPowerOfTwo];
+ }
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public void DoubleCapacity()
+ {
+ Debug.Assert(_entries != null, "Entries should be initialized before doubling the capacity");
+ Debug.Assert(_capacityPowerOfTwo != 0, "Capacity should be not 0");
+ Array.Resize(ref _entries, _capacityPowerOfTwo <<= 1);
+ }
+
+ ///
+ public void GrowCapacity(uint capacity)
+ {
+ if (_capacityPowerOfTwo == 0)
+ {
+ Init(capacity);
+ return;
+ }
+ while (_capacityPowerOfTwo < capacity)
+ DoubleCapacity();
+ }
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public ref T GetSurePresentRef(int index)
+ {
+ Debug.Assert(index >= 0 && index < _capacityPowerOfTwo, $"Index {index} should be in the range of 0 to capacity:{_capacityPowerOfTwo}");
+ return ref _entries.GetSurePresentRef(index);
+ }
+}
+
+// todo: @perf do we actually need the power of 2 size? For example, it is not used for the SmallMap meta/hashes anyway because it is slow
+/// A growing array that grows by appending a new segment doubling the total capacity.
+/// Existing segments are never resized/copied, so item references stay valid (stable).
+/// Random access is still O(1) via power-of-two segment sizes.
+/// Segment sizes starting from first segment F: 0:F, 1:F, 2:2F, 3:4F, 4:8F, ... (first two equal so total doubles each add).
+///
+public struct GrrPowerOf2 : IGrowingArray, IEnumerable
+ where TSegmentsStack : struct, IStack
+{
+ public const int MinCapacityAndFirstSegmentSize = 16;
+
+ internal int _totalCapacityPowerOfTwo;
+ /// Cached first segment length (power of two). Avoids recomputing 1 << bitIndex on the hot path.
+ internal int _firstSegmentCapacity;
+ /// helps to calculate the segment index by subtracting the leading zero count of the global index from this value.
+ internal byte _firstSegmentCapacityLeadingZerosPlusOne;
+
+ internal SmallList _segments;
+
+ /// The number of segments.
+ public int SegmentCount => _segments.Count;
+
+ ///
+ public int Capacity => _totalCapacityPowerOfTwo;
+
+ /// Creates the first segment with capacity rounded up to the next power-of-two (min 16).
+ /// Safe to call again to re-initialize (discards previous segments).
+ [MethodImpl((MethodImplOptions)256)]
+ public void Init(uint capacity)
+ {
+ var firstSegmentCap = capacity <= MinCapacityAndFirstSegmentSize
+ ? MinCapacityAndFirstSegmentSize
+ : (int)BitOps.NextPowerOfTwo(capacity);
+ _totalCapacityPowerOfTwo = firstSegmentCap;
+ _firstSegmentCapacity = firstSegmentCap;
+ _firstSegmentCapacityLeadingZerosPlusOne = (byte)(1 + BitOps.LeadingZeroCount((uint)firstSegmentCap));
+ // Reset so re-Init works (SmallList.InitCount asserts Count == 0)
+ _segments = default;
+ _segments.InitCount(1);
+ _segments.GetSurePresentRef(0) = new T[firstSegmentCap];
+ }
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public void InitDoNotCheckCapacity(uint capacityPowerOfTwo)
+ {
+ _totalCapacityPowerOfTwo = (int)capacityPowerOfTwo; // default is 16 == 0x0...010000
+ _firstSegmentCapacity = (int)capacityPowerOfTwo;
+ _firstSegmentCapacityLeadingZerosPlusOne = (byte)(1 + BitOps.LeadingZeroCount(capacityPowerOfTwo)); // 1 + (32 - 5) == 28 for capacityPowerOfTwo == 16
+
+ // Reset so re-Init works
+ _segments = default;
+ _segments.Count = 1;
+ _segments.GetSurePresentRef(0) = new T[capacityPowerOfTwo];
+ }
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public void DoubleCapacity()
+ {
+ Debug.Assert(_segments.Count != 0,
+ $"Segments should be initialized before doubling the capacity but found {_segments.Count} segments");
+ Debug.Assert(_totalCapacityPowerOfTwo != 0, "Capacity should be non-zero before doubling");
+ // New segment size == current total capacity, so total doubles and old segments stay untouched.
+ _segments.AddDefaultAndGetRef() = new T[_totalCapacityPowerOfTwo];
+ _totalCapacityPowerOfTwo <<= 1;
+ }
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public void GrowCapacity(uint capacity)
+ {
+ if (_totalCapacityPowerOfTwo == 0)
+ {
+ Init(capacity);
+ return;
+ }
+ while ((uint)_totalCapacityPowerOfTwo < capacity)
+ DoubleCapacity();
+ }
+
+ ///
+ [UnscopedRef]
+ [MethodImpl((MethodImplOptions)256)]
+ public ref T GetSurePresentRef(int index)
+ {
+ Debug.Assert(_totalCapacityPowerOfTwo != 0, "Grr should be initialized before accessing items");
+ Debug.Assert(index >= 0 && index < _totalCapacityPowerOfTwo,
+ $"Index {index} should be in the range of 0 to capacity:{_totalCapacityPowerOfTwo}");
+
+ if (index < _firstSegmentCapacity)
+ return ref _segments.GetSurePresentRef(0).GetSurePresentRef(index);
+
+ // Example A: input index = 17, first segment capacity = 16
+ // 28(_firstSegmentCapacityLeadingZerosPlusOne) - 27(LeadingZeroCount(17)) == 1
+ // Example B: input index = 33, first segment capacity = 16
+ // 28(_firstSegmentCapacityLeadingZerosPlusOne) - 26(LeadingZeroCount(31)) == 2
+ var segmentIndex = _firstSegmentCapacityLeadingZerosPlusOne - BitOps.LeadingZeroCount((uint)index);
+
+ Debug.Assert(segmentIndex >= 1 && segmentIndex < _segments.Count,
+ $"segmentIndex {segmentIndex} out of range for index:{index}, segmentCount:{_segments.Count}");
+
+ // Prefix capacity before segment S (S >= 1) is firstCap << (S - 1) == total capacity of segments 0..S-1
+ var insideIndex = index - (_firstSegmentCapacity << (segmentIndex - 1));
+ return ref _segments.GetSurePresentRef(segmentIndex).GetSurePresentRef(insideIndex);
+ }
+
+ /// Returns an enumerator over all allocated slots (Capacity items, not a separate Count).
+ [MethodImpl((MethodImplOptions)256)]
+ public GrrPowerOf2Enumerator GetEnumerator() => new GrrPowerOf2Enumerator(_segments);
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+}
+
+
+/// Enumerator over Grr segment slots (full segment arrays, including unused capacity tails).
+public struct GrrPowerOf2Enumerator : IEnumerator, IEnumerator
+ where TStack : struct, IStack
+{
+ private T _current;
+ private T[] _currentSegment;
+ private int _segmentIndex;
+ private int _insideIndex;
+ private SmallList _segments;
+
+ internal GrrPowerOf2Enumerator(SmallList segments)
+ {
+ _current = default;
+ _segmentIndex = 0;
+ _insideIndex = -1;
+ _segments = segments;
+ _currentSegment = segments.Count == 0 ? null : segments.GetSurePresentRef(0);
+ }
+
+ ///
+ public T Current => _current;
+ object IEnumerator.Current => _current;
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public bool MoveNext()
+ {
+ if (_currentSegment == null)
+ return false;
+
+ if (++_insideIndex >= _currentSegment.Length)
+ {
+ if (++_segmentIndex >= _segments.Count)
+ {
+ _currentSegment = null;
+ return false;
+ }
+ _currentSegment = _segments.GetSurePresentRef(_segmentIndex);
+ Debug.Assert(_currentSegment != null,
+ $"New current segment should not be null for segmentIndex:{_segmentIndex} < _segments.Count:{_segments.Count}");
+ _insideIndex = 0;
+ }
+
+ _current = _currentSegment.GetSurePresentRef(_insideIndex);
+ return true;
+ }
+
+ ///
+ public void Reset()
+ {
+ _current = default;
+ _segmentIndex = 0;
+ _insideIndex = -1;
+ // Keep _segments so Reset+MoveNext can restart over the same data
+ _currentSegment = _segments.Count == 0 ? null : _segments.GetSurePresentRef(0);
+ }
+
+ ///
+ public void Dispose() { }
+}
+
+///
+/// Hybrid growable list: first items live on a fixed ,
+/// overflow lives in a stable segmented (no resize/copy of old heap items).
+/// API mirrors but heap growth keeps references into already-added heap items stable.
+///
+public struct SmallGrrPowerOf2 : IEnumerable
+ where TStack : struct, IStack
+ where TSegStack : struct, IStack
+{
+ /// Number of used items. May decrement to pop if you clear the slot yourself when needed.
+ public int Count;
+
+#pragma warning disable CS0649
+ /// Inline/stack portion of the list.
+ public TStack Stack;
+#pragma warning restore CS0649
+
+ /// Heap overflow stored as stable segments. Valid indexes are 0 .. Count - Stack.Capacity - 1.
+ public GrrPowerOf2 Rest;
+
+ /// Indexer with bounds checks.
+ [UnscopedRef]
+ public ref T this[int index]
+ {
+ [MethodImpl((MethodImplOptions)256)]
+ get
+ {
+ if (index < 0 | index >= Count)
+ return ref SmallList.ThrowIndexOutOfBounds(index, Count);
+ return ref GetSurePresentRef(index);
+ }
+ }
+
+ /// Returns a surely present item by index (no bounds check beyond asserts).
+ [UnscopedRef]
+ [MethodImpl((MethodImplOptions)256)]
+ public ref T GetSurePresentRef(int index)
+ {
+ Debug.Assert(index >= 0 & index < Count, $"SmallGrr index {index} should be in [0, {Count})");
+ var stackCap = Stack.Capacity;
+ if (index < stackCap)
+ return ref Stack.GetSurePresentRef(index);
+ return ref Rest.GetSurePresentRef(index - stackCap);
+ }
+
+ /// Appends a default item and returns a ref to it.
+ [UnscopedRef]
+ [MethodImpl((MethodImplOptions)256)]
+ public ref T AddDefaultAndGetRef()
+ {
+ var index = Count++;
+ var stackCap = Stack.Capacity;
+ if (index < stackCap)
+ return ref Stack.GetSurePresentRef(index);
+
+ var restIndex = index - stackCap;
+ if (restIndex >= Rest.Capacity)
+ Rest.GrowCapacity((uint)restIndex + 1);
+ return ref Rest.GetSurePresentRef(restIndex);
+ }
+
+ /// Appends an item. Returns the index of the added item.
+ [MethodImpl((MethodImplOptions)256)]
+ public int Add(in T item)
+ {
+ AddDefaultAndGetRef() = item;
+ return Count - 1;
+ }
+
+ /// Ensures capacity for at least items (stack + rest).
+ [MethodImpl((MethodImplOptions)256)]
+ public void EnsureCapacity(int count)
+ {
+ Debug.Assert(count >= 0);
+ var stackCap = Stack.Capacity;
+ if (count <= stackCap)
+ return;
+ Rest.GrowCapacity((uint)(count - stackCap));
+ }
+
+ /// Pre-sizes Count and backing storage (like ).
+ [MethodImpl((MethodImplOptions)256)]
+ public void InitCount(int count)
+ {
+ Debug.Assert(count > 0, "Count should be more than 0");
+ Debug.Assert(Count == 0, "Initial the count should be 0");
+ EnsureCapacity(count);
+ Count = count;
+ }
+
+ /// Looks for the item and returns its index, or -1.
+ [MethodImpl((MethodImplOptions)256)]
+ public int TryGetIndex(in T item, TEq eq = default) where TEq : struct, IEq
+ {
+ for (var i = 0; i < Count; ++i)
+ {
+ if (eq.Equals(item, GetSurePresentRef(i)))
+ return i;
+ }
+ return -1;
+ }
+
+ /// Returns index of existing item or appends and returns the new index.
+ [MethodImpl((MethodImplOptions)256)]
+ public int GetIndexOrAdd(in T item, TEq eq = default) where TEq : struct, IEq
+ {
+ var i = TryGetIndex(in item, eq);
+ return i != -1 ? i : Add(in item);
+ }
+
+ /// Clears count; does not free heap segments (they can be reused).
+ [MethodImpl((MethodImplOptions)256)]
+ public void Clear()
+ {
+ var stackCap = Stack.Capacity;
+ var n = Count < stackCap ? Count : stackCap;
+ for (var i = 0; i < n; ++i)
+ Stack.GetSurePresentRef(i) = default;
+ // Leave Rest segments allocated for reuse; clear used rest slots
+ var restCount = Count - stackCap;
+ if (restCount > 0)
+ {
+ for (var i = 0; i < restCount; ++i)
+ Rest.GetSurePresentRef(i) = default;
+ }
+ Count = 0;
+ }
+
+ /// Last item ref; list must be non-empty.
+ [UnscopedRef]
+ [MethodImpl((MethodImplOptions)256)]
+ public ref T GetLastSurePresentItem()
+ {
+ Debug.Assert(Count != 0, "Expecting that the list is not empty");
+ return ref GetSurePresentRef(Count - 1);
+ }
+
+ /// Removes the last item. List must be non-empty.
+ [MethodImpl((MethodImplOptions)256)]
+ public void RemoveLastSurePresentItem()
+ {
+ Debug.Assert(Count != 0, "SmallGrr.RemoveLastSurePresentItem: list should not be empty");
+ GetSurePresentRef(Count - 1) = default;
+ --Count;
+ }
+
+ /// Struct enumerator over used items only (Count).
+ [MethodImpl((MethodImplOptions)256)]
+ public SmallGrrEnumerator GetEnumerator() =>
+ new SmallGrrEnumerator(this);
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+}
+
+/// Enumerator for over used items.
+public struct SmallGrrEnumerator : IEnumerator, IEnumerator
+ where TStack : struct, IStack
+ where TSegStack : struct, IStack
+{
+ private SmallGrrPowerOf2 _list;
+ private int _index;
+ private T _current;
+
+ internal SmallGrrEnumerator(SmallGrrPowerOf2 list)
+ {
+ _list = list;
+ _index = -1;
+ _current = default;
+ }
+
+ ///
+ public T Current => _current;
+ object IEnumerator.Current => _current;
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public bool MoveNext()
+ {
+ var index = ++_index;
+ if (index < _list.Count)
+ {
+ _current = _list.GetSurePresentRef(index);
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ public void Reset() => _index = -1;
+
+ ///
+ public void Dispose() { }
+}
+
+/// Printable thing via provided printer
+public interface IPrintable
+{
+ /// Print to the provided string builder via the provided printer.
+ StringBuilder Print(StringBuilder s, Func printer);
+}
+
+/// Produces good enough hash codes for the fields
+public static class Hasher
+{
+ /// Combines hashes of two fields
+ public static int Combine(T1 a, T2 b)
+ {
+ var bh = b?.GetHashCode() ?? 0;
+ if (ReferenceEquals(a, null))
+ return bh;
+ var ah = a.GetHashCode();
+ if (ah == 0)
+ return bh;
+ return Combine(ah, bh);
+ }
+
+ /// Inspired by System.Tuple.CombineHashCodes
+ [MethodImpl((MethodImplOptions)256)]
+ public static int Combine(int h1, int h2)
+ {
+ unchecked
+ {
+ return (h1 << 5) + h1 ^ h2;
+ }
+ }
+}
+
+/// Configures removed key tombstone, equality and hash function for the SmallMap and friends
+public interface IEq : IEqualityComparer
+{
+ /// Defines the value of the key indicating the removed entry
+ K GetTombstone();
+}
+
+/// Default comparer using the `object.GetHashCode` and `object.Equals` overloads
+public struct DefaultEq : IEq
+{
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public K GetTombstone() => default;
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public bool Equals(K x, K y) => ReferenceEquals(x, y) || x.Equals(y);
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public int GetHashCode(K key) => key.GetHashCode();
+}
+
+/// Uses the integer itself as hash code and `==` for equality
+public struct IntEq : IEq
+{
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public int GetTombstone() => int.MinValue; // todo: @improve separate the tombstone from the hash
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public bool Equals(int x, int y) => x == y;
+
+ ///
+ [MethodImpl((MethodImplOptions)256)]
+ public int GetHashCode(int key) => key;
+}
+
+/// General tools for IEq and its implementations
+public static class Eq
+{
+ /// Factor to multiple by, to achieve the Fibonacci hashing (2^32 / phi, where phi is the golden ratio ~1.61803).
+ public const uint GoldenRatioFactor32 = 2654435769;
+}
+
+/// Instances of the RefEq for the often used K
+public static class RefEq
+{
+ /// RefEq for object, possibly the only thing you need
+ public static readonly RefEq