diff --git a/1.3/Assemblies/1Fishery.dll b/1.3/Assemblies/1Fishery.dll
new file mode 100644
index 0000000..a26c4b7
Binary files /dev/null and b/1.3/Assemblies/1Fishery.dll differ
diff --git a/1.3/Assemblies/IndexRange.dll b/1.3/Assemblies/IndexRange.dll
new file mode 100644
index 0000000..be06f22
Binary files /dev/null and b/1.3/Assemblies/IndexRange.dll differ
diff --git a/1.3/Assemblies/System.Runtime.CompilerServices.Unsafe.dll b/1.3/Assemblies/System.Runtime.CompilerServices.Unsafe.dll
new file mode 100644
index 0000000..c5ba4e4
Binary files /dev/null and b/1.3/Assemblies/System.Runtime.CompilerServices.Unsafe.dll differ
diff --git a/1.4/Assemblies/1Fishery.dll b/1.4/Assemblies/1Fishery.dll
new file mode 100644
index 0000000..46ac14d
Binary files /dev/null and b/1.4/Assemblies/1Fishery.dll differ
diff --git a/1.4/Assemblies/IndexRange.dll b/1.4/Assemblies/IndexRange.dll
new file mode 100644
index 0000000..be06f22
Binary files /dev/null and b/1.4/Assemblies/IndexRange.dll differ
diff --git a/1.4/Assemblies/System.Runtime.CompilerServices.Unsafe.dll b/1.4/Assemblies/System.Runtime.CompilerServices.Unsafe.dll
new file mode 100644
index 0000000..c5ba4e4
Binary files /dev/null and b/1.4/Assemblies/System.Runtime.CompilerServices.Unsafe.dll differ
diff --git a/About/About.xml b/About/About.xml
new file mode 100644
index 0000000..d71e95d
--- /dev/null
+++ b/About/About.xml
@@ -0,0 +1,28 @@
+
+
+ Fishery - Modding Library
+ bs.fishery
+
+ 1.3
+ 1.4
+
+ bradson
+ https://github.com/bbradson/fishery
+
+ Ludeon.RimWorld
+ me.samboycoding.betterloading
+ zetrith.prepatcher
+
+
+ brrainz.harmony
+
+
+
+ brrainz.harmony
+ Harmony
+ steam://url/CommunityFilePage/2009463077
+ https://github.com/pardeike/HarmonyRimWorld/releases/latest
+
+
+ Additions for Harmony to simplify otherwise complex patch operations
+
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index a612ad9..a7064a9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,3 +1,5 @@
+Copyright (c) 2022 bradson
+
Mozilla Public License Version 2.0
==================================
@@ -35,7 +37,7 @@ Mozilla Public License Version 2.0
means any form of the work other than Source Code Form.
1.7. "Larger Work"
- means a work that combines Covered Software with other material, in
+ means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
@@ -357,7 +359,7 @@ Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
@@ -370,4 +372,4 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
- defined by the Mozilla Public License, v. 2.0.
+ defined by the Mozilla Public License, v. 2.0.
\ No newline at end of file
diff --git a/README.md b/README.md
index 568efab..c067c6f 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
# Fishery
- Modding Library
+Modding Library for RimWorld. Used by Performance Fish.
+Useful functions include various abstractions around IL in FishTranspiler, to make writing of transpilers easier and more robust. A couple collections like Dictionary and Span have extension methods for fast and convenient lookups. Some functionalities of Microsoft's Community Toolkit are included, expanded upon and modified to better fit Unity. Equality includes generic extension methods to use in place of Equals on structs without memory allocations. Convert provides a faster, generic alternative to System.Convert.ToType. FisheryLib.Reflection provides a couple reflection helpers, Aliases has a methodof function intended to be utilized with `global using static Aliases` to directly retrieve method infos from static method groups.
diff --git a/Source/FisheryLib.Tests/.editorconfig b/Source/FisheryLib.Tests/.editorconfig
new file mode 100644
index 0000000..6cde0c3
--- /dev/null
+++ b/Source/FisheryLib.Tests/.editorconfig
@@ -0,0 +1,185 @@
+[*.cs]
+
+file_header_template = /* Copyright (c) 2022 bradson\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */
+
+# CA2211: Non-constant fields should not be visible
+dotnet_diagnostic.CA2211.severity = none
+csharp_indent_labels = one_less_than_current
+csharp_using_directive_placement = outside_namespace:suggestion
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = when_multiline:suggestion
+csharp_style_namespace_declarations = file_scoped:suggestion
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_expression_bodied_methods = true:suggestion
+csharp_style_expression_bodied_constructors = true:suggestion
+csharp_style_expression_bodied_operators = true:suggestion
+csharp_style_expression_bodied_properties = true:suggestion
+csharp_style_expression_bodied_indexers = true:suggestion
+csharp_style_expression_bodied_accessors = true:suggestion
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:silent
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+
+[*.{cs,vb}]
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+[*.cs]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.constant_should_be_all_upper.severity = suggestion
+dotnet_naming_rule.constant_should_be_all_upper.symbols = constant
+dotnet_naming_rule.constant_should_be_all_upper.style = all_upper
+
+dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.severity = suggestion
+dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.symbols = private_or_protected_field
+dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.style = camel_case_with__
+
+dotnet_naming_rule.public_or_internal_field_should_be_camel_case.severity = silent
+dotnet_naming_rule.public_or_internal_field_should_be_camel_case.symbols = public_or_internal_field
+dotnet_naming_rule.public_or_internal_field_should_be_camel_case.style = camel_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.constant.applicable_kinds = field, local
+dotnet_naming_symbols.constant.applicable_accessibilities = *
+dotnet_naming_symbols.constant.required_modifiers = const
+
+dotnet_naming_symbols.private_or_protected_field.applicable_kinds = field
+dotnet_naming_symbols.private_or_protected_field.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_or_protected_field.required_modifiers =
+
+dotnet_naming_symbols.public_or_internal_field.applicable_kinds = field
+dotnet_naming_symbols.public_or_internal_field.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_or_internal_field.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.all_upper.required_prefix =
+dotnet_naming_style.all_upper.required_suffix =
+dotnet_naming_style.all_upper.word_separator = _
+dotnet_naming_style.all_upper.capitalization = all_upper
+
+dotnet_naming_style.camel_case_with__.required_prefix = _
+dotnet_naming_style.camel_case_with__.required_suffix =
+dotnet_naming_style.camel_case_with__.word_separator =
+dotnet_naming_style.camel_case_with__.capitalization = camel_case
+
+dotnet_naming_style.camel_case.required_prefix =
+dotnet_naming_style.camel_case.required_suffix =
+dotnet_naming_style.camel_case.word_separator =
+dotnet_naming_style.camel_case.capitalization = camel_case
+dotnet_diagnostic.CA1805.severity = none
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_prefer_utf8_string_literals = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+csharp_prefer_static_local_function = true:suggestion
+csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+csharp_style_prefer_switch_expression = true:suggestion
+csharp_style_prefer_pattern_matching = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_extended_property_pattern = true:suggestion
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+csharp_space_around_binary_operators = before_and_after
+
+[*.{cs,vb}]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+dotnet_diagnostic.CA1838.severity = suggestion
+dotnet_diagnostic.CA1848.severity = suggestion
+dotnet_diagnostic.CA2215.severity = suggestion
+dotnet_style_readonly_field = true:silent
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
+dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
+dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
+dotnet_code_quality_unused_parameters = non_public:suggestion
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+indent_style = tab
diff --git a/Source/FisheryLib.Tests/FisheryLib.Tests.csproj b/Source/FisheryLib.Tests/FisheryLib.Tests.csproj
new file mode 100644
index 0000000..0843495
--- /dev/null
+++ b/Source/FisheryLib.Tests/FisheryLib.Tests.csproj
@@ -0,0 +1,65 @@
+
+
+
+ net48
+ preview
+ True
+ preview-all
+ True
+ v1.3;v1.4
+
+
+
+
+
+
+ E:\RimWorld\1.3\RimWorldWin64_Data\Managed\Assembly-CSharp.dll
+
+
+ E:\RimWorld\1.3\RimWorldWin64_Data\Managed\mscorlib.dll
+
+
+ E:\RimWorld\1.3\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll
+
+
+ E:\RimWorld\1.3\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll
+
+
+ ..\..\1.3\Assemblies\1Fishery.dll
+
+
+
+
+
+
+
+ E:\RimWorld\1.4\RimWorldWin64_Data\Managed\Assembly-CSharp.dll
+
+
+ E:\RimWorld\1.4\RimWorldWin64_Data\Managed\mscorlib.dll
+
+
+ E:\RimWorld\1.4\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll
+
+
+ E:\RimWorld\1.4\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll
+
+
+ ..\..\1.4\Assemblies\1Fishery.dll
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
\ No newline at end of file
diff --git a/Source/FisheryLib.Tests/FisheryLib.Tests.sln b/Source/FisheryLib.Tests/FisheryLib.Tests.sln
new file mode 100644
index 0000000..58c484d
--- /dev/null
+++ b/Source/FisheryLib.Tests/FisheryLib.Tests.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32728.150
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FisheryLib.Tests", "FisheryLib.Tests.csproj", "{5BFAFA41-9B21-4EC3-B3EB-5CBF6D0A300C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ v1.3|Any CPU = v1.3|Any CPU
+ v1.4|Any CPU = v1.4|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5BFAFA41-9B21-4EC3-B3EB-5CBF6D0A300C}.v1.3|Any CPU.ActiveCfg = v1.3|Any CPU
+ {5BFAFA41-9B21-4EC3-B3EB-5CBF6D0A300C}.v1.3|Any CPU.Build.0 = v1.3|Any CPU
+ {5BFAFA41-9B21-4EC3-B3EB-5CBF6D0A300C}.v1.4|Any CPU.ActiveCfg = v1.4|Any CPU
+ {5BFAFA41-9B21-4EC3-B3EB-5CBF6D0A300C}.v1.4|Any CPU.Build.0 = v1.4|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {832FF2F6-0C42-42BD-8599-6288138CCD01}
+ EndGlobalSection
+EndGlobal
diff --git a/Source/FisheryLib.Tests/UnitTest1.cs b/Source/FisheryLib.Tests/UnitTest1.cs
new file mode 100644
index 0000000..8189662
--- /dev/null
+++ b/Source/FisheryLib.Tests/UnitTest1.cs
@@ -0,0 +1,13 @@
+global using System;
+global using Xunit;
+
+namespace FisheryLib.Tests;
+
+public class UnitTest1
+{
+ [Fact]
+ public void Test1()
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Source/FisheryLib/.editorconfig b/Source/FisheryLib/.editorconfig
new file mode 100644
index 0000000..14c2131
--- /dev/null
+++ b/Source/FisheryLib/.editorconfig
@@ -0,0 +1,185 @@
+[*.cs]
+
+file_header_template = Copyright (c) 2022 bradson\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+# CA2211: Non-constant fields should not be visible
+dotnet_diagnostic.CA2211.severity = none
+csharp_indent_labels = one_less_than_current
+csharp_using_directive_placement = outside_namespace:suggestion
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = when_multiline:suggestion
+csharp_style_namespace_declarations = file_scoped:suggestion
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_expression_bodied_methods = true:suggestion
+csharp_style_expression_bodied_constructors = true:suggestion
+csharp_style_expression_bodied_operators = true:suggestion
+csharp_style_expression_bodied_properties = true:suggestion
+csharp_style_expression_bodied_indexers = true:suggestion
+csharp_style_expression_bodied_accessors = true:suggestion
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:silent
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+
+[*.{cs,vb}]
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+[*.cs]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.constant_should_be_all_upper.severity = suggestion
+dotnet_naming_rule.constant_should_be_all_upper.symbols = constant
+dotnet_naming_rule.constant_should_be_all_upper.style = all_upper
+
+dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.severity = suggestion
+dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.symbols = private_or_protected_field
+dotnet_naming_rule.private_or_protected_field_should_be_camel_case_with__.style = camel_case_with__
+
+dotnet_naming_rule.public_or_internal_field_should_be_camel_case.severity = silent
+dotnet_naming_rule.public_or_internal_field_should_be_camel_case.symbols = public_or_internal_field
+dotnet_naming_rule.public_or_internal_field_should_be_camel_case.style = camel_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.constant.applicable_kinds = field, local
+dotnet_naming_symbols.constant.applicable_accessibilities = *
+dotnet_naming_symbols.constant.required_modifiers = const
+
+dotnet_naming_symbols.private_or_protected_field.applicable_kinds = field
+dotnet_naming_symbols.private_or_protected_field.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_or_protected_field.required_modifiers =
+
+dotnet_naming_symbols.public_or_internal_field.applicable_kinds = field
+dotnet_naming_symbols.public_or_internal_field.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_or_internal_field.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.all_upper.required_prefix =
+dotnet_naming_style.all_upper.required_suffix =
+dotnet_naming_style.all_upper.word_separator = _
+dotnet_naming_style.all_upper.capitalization = all_upper
+
+dotnet_naming_style.camel_case_with__.required_prefix = _
+dotnet_naming_style.camel_case_with__.required_suffix =
+dotnet_naming_style.camel_case_with__.word_separator =
+dotnet_naming_style.camel_case_with__.capitalization = camel_case
+
+dotnet_naming_style.camel_case.required_prefix =
+dotnet_naming_style.camel_case.required_suffix =
+dotnet_naming_style.camel_case.word_separator =
+dotnet_naming_style.camel_case.capitalization = camel_case
+dotnet_diagnostic.CA1805.severity = none
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_prefer_utf8_string_literals = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+csharp_prefer_static_local_function = true:suggestion
+csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+csharp_style_prefer_switch_expression = true:suggestion
+csharp_style_prefer_pattern_matching = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_extended_property_pattern = true:suggestion
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+csharp_space_around_binary_operators = before_and_after
+
+[*.{cs,vb}]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+dotnet_diagnostic.CA1838.severity = suggestion
+dotnet_diagnostic.CA1848.severity = suggestion
+dotnet_diagnostic.CA2215.severity = suggestion
+dotnet_style_readonly_field = true:silent
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
+dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion
+dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
+dotnet_code_quality_unused_parameters = non_public:suggestion
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+indent_style = tab
diff --git a/Source/FisheryLib/Aliases.cs b/Source/FisheryLib/Aliases.cs
new file mode 100644
index 0000000..fbaade2
--- /dev/null
+++ b/Source/FisheryLib/Aliases.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2022 bradson
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+global using static FisheryLib.Aliases;
+using System.Runtime.CompilerServices;
+
+namespace FisheryLib;
+
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "alias")]
+public static class Aliases
+{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MethodInfo methodof(Delegate method) => method.Method;
+}
\ No newline at end of file
diff --git a/Source/FisheryLib/ArraySortHelper.cs b/Source/FisheryLib/ArraySortHelper.cs
new file mode 100644
index 0000000..7e91c41
--- /dev/null
+++ b/Source/FisheryLib/ArraySortHelper.cs
@@ -0,0 +1,1163 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using UnityEngine;
+
+namespace FisheryLib;
+#region ArraySortHelper for single arrays
+
+internal sealed class ArraySortHelper : IArraySortHelper
+{
+ public static IArraySortHelper Default { get; } = CreateArraySortHelper();
+
+ private static IArraySortHelper CreateArraySortHelper()
+ => typeof(IComparable).IsAssignableFrom(typeof(T))
+ ? (IArraySortHelper)Activator.CreateInstance(typeof(GenericArraySortHelper<>).MakeGenericType(typeof(T)))
+ : new ArraySortHelper();
+
+ #region IArraySortHelper Members
+
+ public void Sort(Span keys, IComparer? comparer)
+ {
+ // Add a try block here to detect IComparers (or their
+ // underlying IComparables, etc) that are bogus.
+ try
+ {
+ comparer ??= Comparer.Default;
+ IntrospectiveSort(keys, comparer.Compare);
+ }
+ catch (IndexOutOfRangeException)
+ {
+ SortUtils.ThrowArgumentException_BadComparer(comparer);
+ }
+ catch (Exception e)
+ {
+ ThrowHelper.ThrowInvalidOperationException(SortUtils.InvalidOperation_IComparerFailed, e);
+ }
+ }
+
+ #endregion
+
+ public static void Sort(Span keys, Comparison comparer)
+ {
+ Debug.Assert(comparer != null, "Check the arguments in the caller!");
+
+ // Add a try block here to detect bogus comparisons
+ try
+ {
+ IntrospectiveSort(keys, comparer!);
+ }
+ catch (IndexOutOfRangeException)
+ {
+ SortUtils.ThrowArgumentException_BadComparer(comparer);
+ }
+ catch (Exception e)
+ {
+ ThrowHelper.ThrowInvalidOperationException(SortUtils.InvalidOperation_IComparerFailed, e);
+ }
+ }
+
+ private static void SwapIfGreater(Span keys, Comparison comparer, int i, int j)
+ {
+ Debug.Assert(i != j);
+
+ if (comparer(keys[i], keys[j]) > 0)
+ {
+ (keys[j], keys[i]) = (keys[i], keys[j]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Swap(Span a, int i, int j)
+ {
+ Debug.Assert(i != j);
+
+ (a[j], a[i]) = (a[i], a[j]);
+ }
+
+ public static void IntrospectiveSort(Span keys, Comparison comparer)
+ {
+ Debug.Assert(comparer != null);
+
+ if (keys.Length > 1)
+ {
+ IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1), comparer!);
+ }
+ }
+
+ private static void IntroSort(Span keys, int depthLimit, Comparison comparer)
+ {
+ Debug.Assert(!keys.IsEmpty);
+ Debug.Assert(depthLimit >= 0);
+ Debug.Assert(comparer != null);
+
+ var partitionSize = keys.Length;
+ while (partitionSize > 1)
+ {
+ if (partitionSize <= SortUtils.INTROSORT_SIZE_THRESHOLD)
+ {
+
+ if (partitionSize == 2)
+ {
+ SwapIfGreater(keys, comparer!, 0, 1);
+ return;
+ }
+
+ if (partitionSize == 3)
+ {
+ SwapIfGreater(keys, comparer!, 0, 1);
+ SwapIfGreater(keys, comparer!, 0, 2);
+ SwapIfGreater(keys, comparer!, 1, 2);
+ return;
+ }
+
+ InsertionSort(keys[..partitionSize], comparer!);
+ return;
+ }
+
+ if (depthLimit == 0)
+ {
+ HeapSort(keys[..partitionSize], comparer!);
+ return;
+ }
+ depthLimit--;
+
+ var p = PickPivotAndPartition(keys[..partitionSize], comparer!);
+
+ // Note we've already partitioned around the pivot and do not have to move the pivot again.
+ IntroSort(keys[(p + 1)..partitionSize], depthLimit, comparer!);
+ partitionSize = p;
+ }
+ }
+
+ private static int PickPivotAndPartition(Span keys, Comparison comparer)
+ {
+ Debug.Assert(keys.Length >= SortUtils.INTROSORT_SIZE_THRESHOLD);
+ Debug.Assert(comparer != null);
+
+ var hi = keys.Length - 1;
+
+ // Compute median-of-three. But also partition them, since we've done the comparison.
+ var middle = hi >> 1;
+
+ // Sort lo, mid and hi appropriately, then pick mid as the pivot.
+ SwapIfGreater(keys, comparer!, 0, middle); // swap the low with the mid point
+ SwapIfGreater(keys, comparer!, 0, hi); // swap the low with the high
+ SwapIfGreater(keys, comparer!, middle, hi); // swap the middle with the high
+
+ var pivot = keys[middle];
+ Swap(keys, middle, hi - 1);
+ int left = 0, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below.
+
+ while (left < right)
+ {
+ while (comparer!(keys[++left], pivot) < 0)
+ ;
+ while (comparer(pivot, keys[--right]) < 0)
+ ;
+
+ if (left >= right)
+ break;
+
+ Swap(keys, left, right);
+ }
+
+ // Put pivot in the right location.
+ if (left != hi - 1)
+ {
+ Swap(keys, left, hi - 1);
+ }
+ return left;
+ }
+
+ private static void HeapSort(Span keys, Comparison comparer)
+ {
+ Debug.Assert(comparer != null);
+ Debug.Assert(!keys.IsEmpty);
+
+ var n = keys.Length;
+ for (var i = n >> 1; i >= 1; i--)
+ {
+ DownHeap(keys, i, n, comparer!);
+ }
+
+ for (var i = n; i > 1; i--)
+ {
+ Swap(keys, 0, i - 1);
+ DownHeap(keys, 1, i - 1, comparer!);
+ }
+ }
+
+ private static void DownHeap(Span keys, int i, int n, Comparison comparer)
+ {
+ Debug.Assert(comparer != null);
+
+ var d = keys[i - 1];
+ while (i <= n >> 1)
+ {
+ var child = 2 * i;
+ if (child < n && comparer!(keys[child - 1], keys[child]) < 0)
+ {
+ child++;
+ }
+
+ if (!(comparer!(d, keys[child - 1]) < 0))
+ break;
+
+ keys[i - 1] = keys[child - 1];
+ i = child;
+ }
+
+ keys[i - 1] = d;
+ }
+
+ private static void InsertionSort(Span keys, Comparison comparer)
+ {
+ for (var i = 0; i < keys.Length - 1; i++)
+ {
+ var t = keys[i + 1];
+
+ var j = i;
+ while (j >= 0 && comparer(t, keys[j]) < 0)
+ {
+ keys[j + 1] = keys[j];
+ j--;
+ }
+
+ keys[j + 1] = t;
+ }
+ }
+}
+
+internal sealed class GenericArraySortHelper : IArraySortHelper
+ where T : IComparable
+{
+ // Do not add a constructor to this class because ArraySortHelper.CreateSortHelper will not execute it
+
+ #region IArraySortHelper Members
+
+ public void Sort(Span keys, IComparer? comparer)
+ {
+ try
+ {
+ if (comparer == null || comparer == Comparer.Default)
+ {
+ if (keys.Length > 1)
+ {
+ // For floating-point, do a pre-pass to move all NaNs to the beginning
+ // so that we can do an optimized comparison as part of the actual sort
+ // on the remainder of the values.
+ if (typeof(T) == typeof(double) ||
+ typeof(T) == typeof(float))
+ {
+ var nanLeft = SortUtils.MoveNansToFront(keys, default(Span));
+ if (nanLeft == keys.Length)
+ {
+ return;
+ }
+ keys = keys[nanLeft..];
+ }
+
+ IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1));
+ }
+ }
+ else
+ {
+ ArraySortHelper.IntrospectiveSort(keys, comparer.Compare);
+ }
+ }
+ catch (IndexOutOfRangeException)
+ {
+ SortUtils.ThrowArgumentException_BadComparer(comparer);
+ }
+ catch (Exception e)
+ {
+ ThrowHelper.ThrowInvalidOperationException(SortUtils.InvalidOperation_IComparerFailed, e);
+ }
+ }
+
+ #endregion
+
+ /// Swaps the values in the two references if the first is greater than the second.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void SwapIfGreater(ref T i, ref T j)
+ {
+ if (i != null && GreaterThan(ref i, ref j))
+ {
+ Swap(ref i, ref j);
+ }
+ }
+
+ /// Swaps the values in the two references, regardless of whether the two references are the same.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Swap(ref T i, ref T j)
+ {
+ Debug.Assert(!Unsafe.AreSame(ref i, ref j));
+
+ (j, i) = (i, j);
+ }
+
+ private static void IntroSort(Span keys, int depthLimit)
+ {
+ Debug.Assert(!keys.IsEmpty);
+ Debug.Assert(depthLimit >= 0);
+
+ var partitionSize = keys.Length;
+ while (partitionSize > 1)
+ {
+ if (partitionSize <= SortUtils.INTROSORT_SIZE_THRESHOLD)
+ {
+ if (partitionSize == 2)
+ {
+ SwapIfGreater(ref keys[0], ref keys[1]);
+ return;
+ }
+
+ if (partitionSize == 3)
+ {
+ ref var hiRef = ref keys[2];
+ ref var him1Ref = ref keys[1];
+ ref var loRef = ref keys[0];
+
+ SwapIfGreater(ref loRef, ref him1Ref);
+ SwapIfGreater(ref loRef, ref hiRef);
+ SwapIfGreater(ref him1Ref, ref hiRef);
+ return;
+ }
+
+ InsertionSort(keys[..partitionSize]);
+ return;
+ }
+
+ if (depthLimit == 0)
+ {
+ HeapSort(keys[..partitionSize]);
+ return;
+ }
+ depthLimit--;
+
+ var p = PickPivotAndPartition(keys[..partitionSize]);
+
+ // Note we've already partitioned around the pivot and do not have to move the pivot again.
+ IntroSort(keys[(p + 1)..partitionSize], depthLimit);
+ partitionSize = p;
+ }
+ }
+
+ private static int PickPivotAndPartition(Span keys)
+ {
+ Debug.Assert(keys.Length >= SortUtils.INTROSORT_SIZE_THRESHOLD);
+
+ // Use median-of-three to select a pivot. Grab a reference to the 0th, Length-1th, and Length/2th elements, and sort them.
+ ref var zeroRef = ref MemoryMarshal.GetReference(keys);
+ ref var lastRef = ref Unsafe.Add(ref zeroRef, keys.Length - 1);
+ ref var middleRef = ref Unsafe.Add(ref zeroRef, (keys.Length - 1) >> 1);
+ SwapIfGreater(ref zeroRef, ref middleRef);
+ SwapIfGreater(ref zeroRef, ref lastRef);
+ SwapIfGreater(ref middleRef, ref lastRef);
+
+ // Select the middle value as the pivot, and move it to be just before the last element.
+ ref var nextToLastRef = ref Unsafe.Add(ref zeroRef, keys.Length - 2);
+ var pivot = middleRef;
+ Swap(ref middleRef, ref nextToLastRef);
+
+ // Walk the left and right pointers, swapping elements as necessary, until they cross.
+ ref T leftRef = ref zeroRef!, rightRef = ref nextToLastRef!;
+ while (Unsafe.IsAddressLessThan(ref leftRef, ref rightRef))
+ {
+ if (pivot == null)
+ {
+ while (Unsafe.IsAddressLessThan(ref leftRef!, ref nextToLastRef) && (leftRef = ref Unsafe.Add(ref leftRef, 1)) == null)
+ ;
+ while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef) && (rightRef = ref Unsafe.Add(ref rightRef, -1)) != null)
+ ;
+ }
+ else
+ {
+ while (Unsafe.IsAddressLessThan(ref leftRef, ref nextToLastRef) && GreaterThan(ref pivot, ref leftRef = ref Unsafe.Add(ref leftRef, 1)))
+ ;
+ while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef) && LessThan(ref pivot, ref rightRef = ref Unsafe.Add(ref rightRef, -1)))
+ ;
+ }
+
+ if (!Unsafe.IsAddressLessThan(ref leftRef, ref rightRef!))
+ {
+ break;
+ }
+
+ Swap(ref leftRef, ref rightRef);
+ }
+
+ // Put the pivot in the correct location.
+ if (!Unsafe.AreSame(ref leftRef, ref nextToLastRef))
+ {
+ Swap(ref leftRef, ref nextToLastRef);
+ }
+ return (int)((nint)Unsafe.ByteOffset(ref zeroRef, ref leftRef) / Unsafe.SizeOf());
+ }
+
+ private static void HeapSort(Span keys)
+ {
+ Debug.Assert(!keys.IsEmpty);
+
+ var n = keys.Length;
+ for (var i = n >> 1; i >= 1; i--)
+ {
+ DownHeap(keys, i, n);
+ }
+
+ for (var i = n; i > 1; i--)
+ {
+ Swap(ref keys[0], ref keys[i - 1]);
+ DownHeap(keys, 1, i - 1);
+ }
+ }
+
+ private static void DownHeap(Span keys, int i, int n)
+ {
+ var d = keys[i - 1];
+ while (i <= n >> 1)
+ {
+ var child = 2 * i;
+ if (child < n && (keys[child - 1] == null || LessThan(ref keys[child - 1], ref keys[child])))
+ {
+ child++;
+ }
+
+ if (keys[child - 1] == null || !LessThan(ref d, ref keys[child - 1]))
+ break;
+
+ keys[i - 1] = keys[child - 1];
+ i = child;
+ }
+
+ keys[i - 1] = d;
+ }
+
+ private static void InsertionSort(Span keys)
+ {
+ for (var i = 0; i < keys.Length - 1; i++)
+ {
+ var t = Unsafe.Add(ref MemoryMarshal.GetReference(keys), i + 1);
+
+ var j = i;
+ while (j >= 0 && (t == null || LessThan(ref t, ref Unsafe.Add(ref MemoryMarshal.GetReference(keys), j))))
+ {
+ Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) = Unsafe.Add(ref MemoryMarshal.GetReference(keys), j);
+ j--;
+ }
+
+ Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) = t!;
+ }
+ }
+
+ // - These methods exist for use in sorting, where the additional operations present in
+ // the CompareTo methods that would otherwise be used on these primitives add non-trivial overhead,
+ // in particular for floating point where the CompareTo methods need to factor in NaNs.
+ // - The floating-point comparisons here assume no NaNs, which is valid only because the sorting routines
+ // themselves special-case NaN with a pre-pass that ensures none are present in the values being sorted
+ // by moving them all to the front first and then sorting the rest.
+ // - These are duplicated here rather than being on a helper type due to current limitations around generic inlining.
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call
+ private static bool LessThan(ref T left, ref T right)
+ => typeof(T) == typeof(byte)
+ ? (byte)(object)left < (byte)(object)right
+ : typeof(T) == typeof(sbyte)
+ ? (sbyte)(object)left < (sbyte)(object)right
+ : typeof(T) == typeof(ushort)
+ ? (ushort)(object)left < (ushort)(object)right
+ : typeof(T) == typeof(short)
+ ? (short)(object)left < (short)(object)right
+ : typeof(T) == typeof(uint)
+ ? (uint)(object)left < (uint)(object)right
+ : typeof(T) == typeof(int)
+ ? (int)(object)left < (int)(object)right
+ : typeof(T) == typeof(ulong)
+ ? (ulong)(object)left < (ulong)(object)right
+ : typeof(T) == typeof(long)
+ ? (long)(object)left < (long)(object)right
+ : typeof(T) == typeof(nuint)
+ ? (nuint)(object)left < (nuint)(object)right
+ : typeof(T) == typeof(nint)
+ ? (nint)(object)left < (nint)(object)right
+ : typeof(T) == typeof(float)
+ ? (float)(object)left < (float)(object)right
+ : typeof(T) == typeof(double)
+ ? (double)(object)left < (double)(object)right
+ : left.CompareTo(right) < 0;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call
+ private static bool GreaterThan(ref T left, ref T right)
+ => typeof(T) == typeof(byte)
+ ? (byte)(object)left > (byte)(object)right
+ : typeof(T) == typeof(sbyte)
+ ? (sbyte)(object)left > (sbyte)(object)right
+ : typeof(T) == typeof(ushort)
+ ? (ushort)(object)left > (ushort)(object)right
+ : typeof(T) == typeof(short)
+ ? (short)(object)left > (short)(object)right
+ : typeof(T) == typeof(uint)
+ ? (uint)(object)left > (uint)(object)right
+ : typeof(T) == typeof(int)
+ ? (int)(object)left > (int)(object)right
+ : typeof(T) == typeof(ulong)
+ ? (ulong)(object)left > (ulong)(object)right
+ : typeof(T) == typeof(long)
+ ? (long)(object)left > (long)(object)right
+ : typeof(T) == typeof(nuint)
+ ? (nuint)(object)left > (nuint)(object)right
+ : typeof(T) == typeof(nint)
+ ? (nint)(object)left > (nint)(object)right
+ : typeof(T) == typeof(float)
+ ? (float)(object)left > (float)(object)right
+ : typeof(T) == typeof(double)
+ ? (double)(object)left > (double)(object)right
+ : left.CompareTo(right) > 0;
+}
+
+#endregion
+
+#region ArraySortHelper for paired key and value arrays
+
+internal sealed class ArraySortHelper : IArraySortHelper
+{
+ public static IArraySortHelper Default { get; } = CreateArraySortHelper();
+
+ private static IArraySortHelper CreateArraySortHelper()
+ => typeof(IComparable).IsAssignableFrom(typeof(TKey))
+ ? (IArraySortHelper)Activator.CreateInstance(typeof(GenericArraySortHelper<,>).MakeGenericType(typeof(TKey), typeof(TValue)))
+ : new ArraySortHelper();
+
+ public void Sort(Span keys, Span values, IComparer? comparer)
+ {
+ // Add a try block here to detect IComparers (or their
+ // underlying IComparables, etc) that are bogus.
+ try
+ {
+ IntrospectiveSort(keys, values, comparer ?? Comparer.Default);
+ }
+ catch (IndexOutOfRangeException)
+ {
+ SortUtils.ThrowArgumentException_BadComparer(comparer);
+ }
+ catch (Exception e)
+ {
+ ThrowHelper.ThrowInvalidOperationException(SortUtils.InvalidOperation_IComparerFailed, e);
+ }
+ }
+
+ private static void SwapIfGreaterWithValues(Span keys, Span values, IComparer comparer, int i, int j)
+ {
+ Debug.Assert(comparer != null);
+ Debug.Assert(0 <= i && i < keys.Length && i < values.Length);
+ Debug.Assert(0 <= j && j < keys.Length && j < values.Length);
+ Debug.Assert(i != j);
+
+ if (comparer!.Compare(keys[i], keys[j]) > 0)
+ {
+ (keys[j], keys[i]) = (keys[i], keys[j]);
+
+ (values[j], values[i]) = (values[i], values[j]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Swap(Span keys, Span values, int i, int j)
+ {
+ Debug.Assert(i != j);
+
+ (keys[j], keys[i]) = (keys[i], keys[j]);
+
+ (values[j], values[i]) = (values[i], values[j]);
+ }
+
+ public static void IntrospectiveSort(Span keys, Span values, IComparer comparer)
+ {
+ Debug.Assert(comparer != null);
+ Debug.Assert(keys.Length == values.Length);
+
+ if (keys.Length > 1)
+ {
+ IntroSort(keys, values, 2 * (BitOperations.Log2((uint)keys.Length) + 1), comparer!);
+ }
+ }
+
+ public static void IntroSort(Span keys, Span values, int depthLimit, IComparer comparer)
+ {
+ Debug.Assert(!keys.IsEmpty);
+ Debug.Assert(values.Length == keys.Length);
+ Debug.Assert(depthLimit >= 0);
+ Debug.Assert(comparer != null);
+
+ var partitionSize = keys.Length;
+ while (partitionSize > 1)
+ {
+ if (partitionSize <= SortUtils.INTROSORT_SIZE_THRESHOLD)
+ {
+
+ if (partitionSize == 2)
+ {
+ SwapIfGreaterWithValues(keys, values, comparer!, 0, 1);
+ return;
+ }
+
+ if (partitionSize == 3)
+ {
+ SwapIfGreaterWithValues(keys, values, comparer!, 0, 1);
+ SwapIfGreaterWithValues(keys, values, comparer!, 0, 2);
+ SwapIfGreaterWithValues(keys, values, comparer!, 1, 2);
+ return;
+ }
+
+ InsertionSort(keys[..partitionSize], values[..partitionSize], comparer!);
+ return;
+ }
+
+ if (depthLimit == 0)
+ {
+ HeapSort(keys[..partitionSize], values[..partitionSize], comparer!);
+ return;
+ }
+ depthLimit--;
+
+ var p = PickPivotAndPartition(keys[..partitionSize], values[..partitionSize], comparer!);
+
+ // Note we've already partitioned around the pivot and do not have to move the pivot again.
+ IntroSort(keys[(p + 1)..partitionSize], values[(p + 1)..partitionSize], depthLimit, comparer!);
+ partitionSize = p;
+ }
+ }
+
+ private static int PickPivotAndPartition(Span keys, Span values, IComparer comparer)
+ {
+ Debug.Assert(keys.Length >= SortUtils.INTROSORT_SIZE_THRESHOLD);
+ Debug.Assert(comparer != null);
+
+ var hi = keys.Length - 1;
+
+ // Compute median-of-three. But also partition them, since we've done the comparison.
+ var middle = hi >> 1;
+
+ // Sort lo, mid and hi appropriately, then pick mid as the pivot.
+ SwapIfGreaterWithValues(keys, values, comparer!, 0, middle); // swap the low with the mid point
+ SwapIfGreaterWithValues(keys, values, comparer!, 0, hi); // swap the low with the high
+ SwapIfGreaterWithValues(keys, values, comparer!, middle, hi); // swap the middle with the high
+
+ var pivot = keys[middle];
+ Swap(keys, values, middle, hi - 1);
+ int left = 0, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below.
+
+ while (left < right)
+ {
+ while (comparer!.Compare(keys[++left], pivot) < 0)
+ ;
+ while (comparer.Compare(pivot, keys[--right]) < 0)
+ ;
+
+ if (left >= right)
+ break;
+
+ Swap(keys, values, left, right);
+ }
+
+ // Put pivot in the right location.
+ if (left != hi - 1)
+ {
+ Swap(keys, values, left, hi - 1);
+ }
+ return left;
+ }
+
+ private static void HeapSort(Span keys, Span values, IComparer comparer)
+ {
+ Debug.Assert(comparer != null);
+ Debug.Assert(!keys.IsEmpty);
+
+ var n = keys.Length;
+ for (var i = n >> 1; i >= 1; i--)
+ {
+ DownHeap(keys, values, i, n, comparer!);
+ }
+
+ for (var i = n; i > 1; i--)
+ {
+ Swap(keys, values, 0, i - 1);
+ DownHeap(keys, values, 1, i - 1, comparer!);
+ }
+ }
+
+ private static void DownHeap(Span keys, Span values, int i, int n, IComparer comparer)
+ {
+ Debug.Assert(comparer != null);
+
+ var d = keys[i - 1];
+ var dValue = values[i - 1];
+
+ while (i <= n >> 1)
+ {
+ var child = 2 * i;
+ if (child < n && comparer!.Compare(keys[child - 1], keys[child]) < 0)
+ {
+ child++;
+ }
+
+ if (!(comparer!.Compare(d, keys[child - 1]) < 0))
+ break;
+
+ keys[i - 1] = keys[child - 1];
+ values[i - 1] = values[child - 1];
+ i = child;
+ }
+
+ keys[i - 1] = d;
+ values[i - 1] = dValue;
+ }
+
+ private static void InsertionSort(Span keys, Span values, IComparer comparer)
+ {
+ Debug.Assert(comparer != null);
+
+ for (var i = 0; i < keys.Length - 1; i++)
+ {
+ var t = keys[i + 1];
+ var tValue = values[i + 1];
+
+ var j = i;
+ while (j >= 0 && comparer!.Compare(t, keys[j]) < 0)
+ {
+ keys[j + 1] = keys[j];
+ values[j + 1] = values[j];
+ j--;
+ }
+
+ keys[j + 1] = t;
+ values[j + 1] = tValue;
+ }
+ }
+}
+
+internal sealed class GenericArraySortHelper : IArraySortHelper
+ where TKey : IComparable
+{
+ public void Sort(Span keys, Span values, IComparer? comparer)
+ {
+ // Add a try block here to detect IComparers (or their
+ // underlying IComparables, etc) that are bogus.
+ try
+ {
+ if (comparer == null || comparer == Comparer.Default)
+ {
+ if (keys.Length > 1)
+ {
+ // For floating-point, do a pre-pass to move all NaNs to the beginning
+ // so that we can do an optimized comparison as part of the actual sort
+ // on the remainder of the values.
+ if (typeof(TKey) == typeof(double) ||
+ typeof(TKey) == typeof(float))
+ {
+ var nanLeft = SortUtils.MoveNansToFront(keys, values);
+ if (nanLeft == keys.Length)
+ {
+ return;
+ }
+ keys = keys[nanLeft..];
+ values = values[nanLeft..];
+ }
+
+ IntroSort(keys, values, 2 * (BitOperations.Log2((uint)keys.Length) + 1));
+ }
+ }
+ else
+ {
+ ArraySortHelper.IntrospectiveSort(keys, values, comparer);
+ }
+ }
+ catch (IndexOutOfRangeException)
+ {
+ SortUtils.ThrowArgumentException_BadComparer(comparer);
+ }
+ catch (Exception e)
+ {
+ ThrowHelper.ThrowInvalidOperationException(SortUtils.InvalidOperation_IComparerFailed, e);
+ }
+ }
+
+ private static void SwapIfGreaterWithValues(Span keys, Span values, int i, int j)
+ {
+ Debug.Assert(i != j);
+
+ ref var keyRef = ref keys[i];
+ if (keyRef != null && GreaterThan(ref keyRef, ref keys[j]))
+ {
+ var key = keyRef;
+ keys[i] = keys[j];
+ keys[j] = key;
+
+ (values[j], values[i]) = (values[i], values[j]);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Swap(Span keys, Span values, int i, int j)
+ {
+ Debug.Assert(i != j);
+
+ (keys[j], keys[i]) = (keys[i], keys[j]);
+
+ (values[j], values[i]) = (values[i], values[j]);
+ }
+
+ private static void IntroSort(Span keys, Span values, int depthLimit)
+ {
+ Debug.Assert(!keys.IsEmpty);
+ Debug.Assert(values.Length == keys.Length);
+ Debug.Assert(depthLimit >= 0);
+
+ var partitionSize = keys.Length;
+ while (partitionSize > 1)
+ {
+ if (partitionSize <= SortUtils.INTROSORT_SIZE_THRESHOLD)
+ {
+
+ if (partitionSize == 2)
+ {
+ SwapIfGreaterWithValues(keys, values, 0, 1);
+ return;
+ }
+
+ if (partitionSize == 3)
+ {
+ SwapIfGreaterWithValues(keys, values, 0, 1);
+ SwapIfGreaterWithValues(keys, values, 0, 2);
+ SwapIfGreaterWithValues(keys, values, 1, 2);
+ return;
+ }
+
+ InsertionSort(keys[..partitionSize], values[..partitionSize]);
+ return;
+ }
+
+ if (depthLimit == 0)
+ {
+ HeapSort(keys[..partitionSize], values[..partitionSize]);
+ return;
+ }
+ depthLimit--;
+
+ var p = PickPivotAndPartition(keys[..partitionSize], values[..partitionSize]);
+
+ // Note we've already partitioned around the pivot and do not have to move the pivot again.
+ IntroSort(keys[(p + 1)..partitionSize], values[(p + 1)..partitionSize], depthLimit);
+ partitionSize = p;
+ }
+ }
+
+ private static int PickPivotAndPartition(Span keys, Span values)
+ {
+ Debug.Assert(keys.Length >= SortUtils.INTROSORT_SIZE_THRESHOLD);
+
+ var hi = keys.Length - 1;
+
+ // Compute median-of-three. But also partition them, since we've done the comparison.
+ var middle = hi >> 1;
+
+ // Sort lo, mid and hi appropriately, then pick mid as the pivot.
+ SwapIfGreaterWithValues(keys, values, 0, middle); // swap the low with the mid point
+ SwapIfGreaterWithValues(keys, values, 0, hi); // swap the low with the high
+ SwapIfGreaterWithValues(keys, values, middle, hi); // swap the middle with the high
+
+ var pivot = keys[middle];
+ Swap(keys, values, middle, hi - 1);
+ int left = 0, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below.
+
+ while (left < right)
+ {
+ if (pivot == null)
+ {
+ while (left < (hi - 1) && keys[++left] == null)
+ ;
+ while (right > 0 && keys[--right] != null)
+ ;
+ }
+ else
+ {
+ while (GreaterThan(ref pivot, ref keys[++left]))
+ ;
+ while (LessThan(ref pivot, ref keys[--right]))
+ ;
+ }
+
+ if (left >= right)
+ break;
+
+ Swap(keys, values, left, right);
+ }
+
+ // Put pivot in the right location.
+ if (left != hi - 1)
+ {
+ Swap(keys, values, left, hi - 1);
+ }
+ return left;
+ }
+
+ private static void HeapSort(Span keys, Span values)
+ {
+ Debug.Assert(!keys.IsEmpty);
+
+ var n = keys.Length;
+ for (var i = n >> 1; i >= 1; i--)
+ {
+ DownHeap(keys, values, i, n);
+ }
+
+ for (var i = n; i > 1; i--)
+ {
+ Swap(keys, values, 0, i - 1);
+ DownHeap(keys, values, 1, i - 1);
+ }
+ }
+
+ private static void DownHeap(Span keys, Span values, int i, int n)
+ {
+ var d = keys[i - 1];
+ var dValue = values[i - 1];
+
+ while (i <= n >> 1)
+ {
+ var child = 2 * i;
+ if (child < n && (keys[child - 1] == null || LessThan(ref keys[child - 1], ref keys[child])))
+ {
+ child++;
+ }
+
+ if (keys[child - 1] == null || !LessThan(ref d, ref keys[child - 1]))
+ break;
+
+ keys[i - 1] = keys[child - 1];
+ values[i - 1] = values[child - 1];
+ i = child;
+ }
+
+ keys[i - 1] = d;
+ values[i - 1] = dValue;
+ }
+
+ private static void InsertionSort(Span keys, Span values)
+ {
+ for (var i = 0; i < keys.Length - 1; i++)
+ {
+ var t = keys[i + 1];
+ var tValue = values[i + 1];
+
+ var j = i;
+ while (j >= 0 && (t == null || LessThan(ref t, ref keys[j])))
+ {
+ keys[j + 1] = keys[j];
+ values[j + 1] = values[j];
+ j--;
+ }
+
+ keys[j + 1] = t!;
+ values[j + 1] = tValue;
+ }
+ }
+
+ // - These methods exist for use in sorting, where the additional operations present in
+ // the CompareTo methods that would otherwise be used on these primitives add non-trivial overhead,
+ // in particular for floating point where the CompareTo methods need to factor in NaNs.
+ // - The floating-point comparisons here assume no NaNs, which is valid only because the sorting routines
+ // themselves special-case NaN with a pre-pass that ensures none are present in the values being sorted
+ // by moving them all to the front first and then sorting the rest.
+ // - These are duplicated here rather than being on a helper type due to current limitations around generic inlining.
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call
+ private static bool LessThan(ref TKey left, ref TKey right)
+ => typeof(TKey) == typeof(byte)
+ ? (byte)(object)left < (byte)(object)right
+ : typeof(TKey) == typeof(sbyte)
+ ? (sbyte)(object)left < (sbyte)(object)right
+ : typeof(TKey) == typeof(ushort)
+ ? (ushort)(object)left < (ushort)(object)right
+ : typeof(TKey) == typeof(short)
+ ? (short)(object)left < (short)(object)right
+ : typeof(TKey) == typeof(uint)
+ ? (uint)(object)left < (uint)(object)right
+ : typeof(TKey) == typeof(int)
+ ? (int)(object)left < (int)(object)right
+ : typeof(TKey) == typeof(ulong)
+ ? (ulong)(object)left < (ulong)(object)right
+ : typeof(TKey) == typeof(long)
+ ? (long)(object)left < (long)(object)right
+ : typeof(TKey) == typeof(nuint)
+ ? (nuint)(object)left < (nuint)(object)right
+ : typeof(TKey) == typeof(nint)
+ ? (nint)(object)left < (nint)(object)right
+ : typeof(TKey) == typeof(float)
+ ? (float)(object)left < (float)(object)right
+ : typeof(TKey) == typeof(double)
+ ? (double)(object)left < (double)(object)right
+ : left.CompareTo(right) < 0;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call
+ private static bool GreaterThan(ref TKey left, ref TKey right)
+ => typeof(TKey) == typeof(byte)
+ ? (byte)(object)left > (byte)(object)right
+ : typeof(TKey) == typeof(sbyte)
+ ? (sbyte)(object)left > (sbyte)(object)right
+ : typeof(TKey) == typeof(ushort)
+ ? (ushort)(object)left > (ushort)(object)right
+ : typeof(TKey) == typeof(short)
+ ? (short)(object)left > (short)(object)right
+ : typeof(TKey) == typeof(uint)
+ ? (uint)(object)left > (uint)(object)right
+ : typeof(TKey) == typeof(int)
+ ? (int)(object)left > (int)(object)right
+ : typeof(TKey) == typeof(ulong)
+ ? (ulong)(object)left > (ulong)(object)right
+ : typeof(TKey) == typeof(long)
+ ? (long)(object)left > (long)(object)right
+ : typeof(TKey) == typeof(nuint)
+ ? (nuint)(object)left > (nuint)(object)right
+ : typeof(TKey) == typeof(nint)
+ ? (nint)(object)left > (nint)(object)right
+ : typeof(TKey) == typeof(float)
+ ? (float)(object)left > (float)(object)right
+ : typeof(TKey) == typeof(double)
+ ? (double)(object)left > (double)(object)right
+ : left.CompareTo(right) > 0;
+}
+
+#endregion
+
+/// Helper methods for use in array/span sorting routines.
+internal static class SortUtils
+{
+ public static int MoveNansToFront(Span keys, Span values) where TKey : notnull
+ {
+ Debug.Assert(typeof(TKey) == typeof(double) || typeof(TKey) == typeof(float));
+
+ var left = 0;
+
+ for (var i = 0; i < keys.Length; i++)
+ {
+ if ((typeof(TKey) == typeof(double) && double.IsNaN((double)(object)keys[i])) ||
+ (typeof(TKey) == typeof(float) && float.IsNaN((float)(object)keys[i])))
+ {
+ (keys[i], keys[left]) = (keys[left], keys[i]);
+
+ if ((uint)i < (uint)values.Length) // check to see if we have values
+ {
+ (values[i], values[left]) = (values[left], values[i]);
+ }
+
+ left++;
+ }
+ }
+
+ return left;
+ }
+
+ // This is the threshold where Introspective sort switches to Insertion sort.
+ // Empirically, 16 seems to speed up most cases without slowing down others, at least for integers.
+ // Large value types may benefit from a smaller number.
+ internal const int INTROSORT_SIZE_THRESHOLD = 16;
+
+ [DoesNotReturn]
+ internal static void ThrowArgumentException_BadComparer(object? comparer)
+ => throw new ArgumentException(string.Format(Arg_BogusIComparer, comparer));
+
+ internal static string InvalidOperation_IComparerFailed => "Failed to compare two elements in the array.";
+ internal static string Arg_BogusIComparer
+ => "Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'.";
+}
+
+internal interface IArraySortHelper
+{
+ void Sort(Span keys, Span values, IComparer? comparer);
+}
+
+internal interface IArraySortHelper
+{
+ void Sort(Span keys, IComparer? comparer);
+}
+
+internal static class BitOperations
+{
+ private static ReadOnlySpan Log2DeBruijn
+ => new byte[32]
+ {
+ 00, 09, 01, 10, 13, 21, 02, 29,
+ 11, 14, 16, 18, 22, 25, 03, 30,
+ 08, 12, 20, 28, 15, 17, 24, 07,
+ 19, 27, 23, 06, 26, 05, 04, 31
+ };
+
+ ///
+ /// Returns the integer (floor) log of the specified value, base 2.
+ /// Note that by convention, input value 0 returns 0 since log(0) is undefined.
+ ///
+ /// The value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ //[CLSCompliant(false)]
+ public static int Log2(uint value)
+ {
+ // The 0->0 contract is fulfilled by setting the LSB to 1.
+ // Log(1) is 0, and setting the LSB for values > 1 does not change the log2 result.
+ value |= 1;
+
+#if probablyNotSupportedOnUnity
+ // value lzcnt actual expected
+ // ..0001 31 31-31 0
+ // ..0010 30 31-30 1
+ // 0010.. 2 31-2 29
+ // 0100.. 1 31-1 30
+ // 1000.. 0 31-0 31
+ if (Lzcnt.IsSupported)
+ {
+ return 31 ^ (int)Lzcnt.LeadingZeroCount(value);
+ }
+
+ if (ArmBase.IsSupported)
+ {
+ return 31 ^ ArmBase.LeadingZeroCount(value);
+ }
+
+ // BSR returns the log2 result directly. However BSR is slower than LZCNT
+ // on AMD processors, so we leave it as a fallback only.
+ if (X86Base.IsSupported)
+ {
+ return (int)X86Base.BitScanReverse(value);
+ }
+#endif
+
+ // Fallback contract is 0->0
+ return Log2SoftwareFallback(value);
+ }
+
+ ///
+ /// Returns the integer (floor) log of the specified value, base 2.
+ /// Note that by convention, input value 0 returns 0 since Log(0) is undefined.
+ /// Does not directly use any hardware intrinsics, nor does it incur branching.
+ ///
+ /// The value.
+ private static int Log2SoftwareFallback(uint value)
+ {
+ // No AggressiveInlining due to large method size
+ // Has conventional contract 0->0 (Log(0) is undefined)
+
+ // Fill trailing zeros with ones, eg 00010010 becomes 00011111
+ value |= value >> 01;
+ value |= value >> 02;
+ value |= value >> 04;
+ value |= value >> 08;
+ value |= value >> 16;
+
+ // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check
+ return Unsafe.AddByteOffset(
+ // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_1100_0100_1010_1100_1101_1101u
+ ref MemoryMarshal.GetReference(Log2DeBruijn),
+ // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here
+ (IntPtr)(int)((value * 0x07C4ACDDu) >> 27));
+ }
+}
\ No newline at end of file
diff --git a/Source/FisheryLib/BoolExtensions.cs b/Source/FisheryLib/BoolExtensions.cs
new file mode 100644
index 0000000..5942908
--- /dev/null
+++ b/Source/FisheryLib/BoolExtensions.cs
@@ -0,0 +1,77 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// https://github.com/CommunityToolkit/dotnet/blob/main/CommunityToolkit.HighPerformance/Extensions/BoolExtensions.cs
+
+using System.Runtime.CompilerServices;
+
+namespace FisheryLib;
+
+///
+/// Helpers for working with the type.
+///
+public static class BoolExtensions
+{
+ ///
+ /// Converts the given value into a .
+ ///
+ /// The input value to convert.
+ /// 1 if is , 0 otherwise.
+ /// This method does not contain branching instructions.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe byte ToByte(this bool flag)
+ {
+ // Whenever we need to take the address of an argument, we make a local copy first.
+ // This will be removed by the JIT anyway, but it can help produce better codegen and
+ // remove unwanted stack spills if the caller is using constant arguments. This is
+ // because taking the address of an argument can interfere with some of the flow
+ // analysis executed by the JIT, which can in some cases block constant propagation.
+ bool copy = flag;
+
+ return *(byte*)©
+ }
+
+ ///
+ /// Converts the given value to an mask with
+ /// all bits representing the value of the input flag (either 0xFFFFFFFF or 0x00000000).
+ ///
+ /// The input value to convert.
+ /// 0xFFFFFFFF if is , 0x00000000 otherwise.
+ ///
+ /// This method does not contain branching instructions, and it is only guaranteed to work with
+ /// values being either 0 or 1. Operations producing a result,
+ /// such as numerical comparisons, always result in a valid value. If the value is
+ /// produced by fields with a custom ,
+ /// or by using or other unsafe APIs to directly manipulate the underlying
+ /// data though, it is responsibility of the caller to ensure the validity of the provided value.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe int ToBitwiseMask32(this bool flag)
+ {
+ bool copy = flag;
+ byte rangeFlag = *(byte*)©
+ int negativeFlag = rangeFlag - 1;
+ int mask = ~negativeFlag;
+
+ return mask;
+ }
+
+ ///
+ /// Converts the given value to a mask with
+ /// all bits representing the value of the input flag (either all 1s or 0s).
+ ///
+ /// The input value to convert.
+ /// All 1s if is , all 0s otherwise.
+ /// This method does not contain branching instructions. See additional note in .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe long ToBitwiseMask64(this bool flag)
+ {
+ bool copy = flag;
+ byte rangeFlag = *(byte*)©
+ long negativeFlag = (long)rangeFlag - 1;
+ long mask = ~negativeFlag;
+
+ return mask;
+ }
+}
\ No newline at end of file
diff --git a/Source/FisheryLib/CollectionExtensions.cs b/Source/FisheryLib/CollectionExtensions.cs
new file mode 100644
index 0000000..136f7de
--- /dev/null
+++ b/Source/FisheryLib/CollectionExtensions.cs
@@ -0,0 +1,333 @@
+// Copyright (c) 2022 bradson
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace FisheryLib;
+public static class CollectionExtensions
+{
+ public static bool TryGetItem(this List list, int index, [NotNullWhen(true)] out T? item)
+ {
+ if (list.Count > index)
+ {
+ item = list[index]!;
+ return true;
+ }
+
+ item = default;
+ return false;
+ }
+
+ ///
+ /// Taken from Span<>.this[] and slower than List<>.this[] due to the ArrayAdjustment
+ ///
+ internal static T GetItemUnchecked(this List list, int index)
+ => Unsafe.Add(ref Unsafe.AddByteOffset(ref Unsafe.As>(list._items).Data, PerTypeValues.ArrayAdjustment), index)!;
+
+ [StructLayout(LayoutKind.Sequential)]
+ private sealed class Pinnable
+ {
+ public T? Data;
+ }
+ private static class PerTypeValues
+ {
+ public static readonly IntPtr ArrayAdjustment = MeasureArrayAdjustment();
+ private static IntPtr MeasureArrayAdjustment()
+ {
+ var array = new T[1];
+ return Unsafe.ByteOffset(ref Unsafe.As>(array).Data!, ref array[0]);
+ }
+ }
+
+ public static bool TryGetItem(this T[] array, int index, out T? item)
+ {
+ if (array.Length > index)
+ {
+ item = array[index];
+ return true;
+ }
+
+ item = default;
+ return false;
+ }
+
+ public static T[] ToArray(this IEnumerable enumerable, int length)
+ {
+ var array = new T[length];
+
+ if (enumerable is IList ilist)
+ array.Fill(ilist);
+ else
+ array.FillUsingEnumerable(enumerable);
+
+ return array;
+ }
+
+ public static Telement[] ToArray(this Tenumerable enumerable, Telement[] destination)
+ where Tenumerable : IList
+ {
+ Guard.IsNotNull(destination);
+
+ destination.Fill(enumerable);
+ return destination;
+ }
+
+ public static T[] ToArray(this IEnumerable enumerable, T[] destination)
+ {
+ Guard.IsNotNull(destination);
+
+ if (enumerable is IList ilist)
+ destination.Fill(ilist);
+ else
+ destination.FillUsingEnumerable(enumerable);
+
+ return destination;
+ }
+
+ private static void FillUsingEnumerable(this T[] array, IEnumerable enumerable, int startIndex = 0, int count = -1)
+ {
+ var i = 0u;
+ foreach (var item in enumerable)
+ {
+ if (i >= (uint)count || startIndex >= array.Length)
+ break;
+
+ array[startIndex++] = item;
+ i++;
+ }
+ }
+
+ public static void Fill(this Telement[] array, Tcollection collection, int startIndex = 0, int count = -1)
+ where Tcollection : IList
+ {
+ Guard.IsNotNull(collection);
+
+ if (count <= 0)
+ count = collection.Count;
+
+ if (count <= array.Length - startIndex)
+ {
+ collection.CopyTo(array, startIndex);
+ return;
+ }
+
+ for (var listIndex = 0; listIndex < count && startIndex < array.Length; startIndex++, listIndex++)
+ array[startIndex] = collection[listIndex];
+ }
+
+ public static void Fill(this T[] array, IEnumerable enumerable, int startIndex = 0, int count = -1)
+ {
+ Guard.IsNotNull(enumerable);
+
+ if (enumerable is IList ilist)
+ array.Fill(ilist, startIndex, count);
+ else
+ array.FillUsingEnumerable(enumerable, startIndex, count);
+ }
+
+ public static ref TValue GetReference(this Dictionary dictionary, TKey key)
+ {
+ if (dictionary.buckets != null)
+ {
+ var keyCode = HashCode.Get(key) & 0x7FFFFFFF; // HashCode.Get can handle null, & 0x7FFFFFFF removes any sign
+
+ var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ while (bucket >= 0)
+ {
+ ref var entry = ref dictionary.entries[bucket];
+ if (entry.hashCode == keyCode && entry.key.Equals(key))
+ return ref entry.value;
+
+ bucket = entry.next;
+ }
+ }
+ return ref ThrowKeyNotFoundException(key);
+ }
+
+ ///
+ /// Returns a reference to the value field of an entry if the key exists and an Unsafe.NullRef{TValue} if not.
+ /// Must be checked with Unsafe.IsNullRef before assigning to a regular var or field without ref.
+ ///
+ /// The key
+ /// ref TValue or Unsafe.NullRef{TValue}
+ public static ref TValue TryGetReferenceUnsafe(this Dictionary dictionary, TKey key)
+ {
+ if (dictionary.buckets != null)
+ {
+ var keyCode = HashCode.Get(key) & 0x7FFFFFFF;
+
+ var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ while (bucket >= 0)
+ {
+ ref var entry = ref dictionary.entries[bucket];
+ if (entry.hashCode == keyCode && entry.key.Equals(key))
+ return ref entry.value;
+
+ bucket = entry.next;
+ }
+ }
+ return ref Unsafe.NullRef();
+ }
+
+ public static ref TValue TryGetReferenceUnsafe(this Dictionary dictionary, ref TKey key)
+ where TKey : struct
+ {
+ if (dictionary.buckets != null)
+ {
+ var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
+
+ var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ while (bucket >= 0)
+ {
+ ref var entry = ref dictionary.entries[bucket];
+ if (entry.hashCode == keyCode && entry.key.Equals(ref key))
+ return ref entry.value;
+
+ bucket = entry.next;
+ }
+ }
+ return ref Unsafe.NullRef();
+ }
+
+ public static TValue? TryGetValue(this Dictionary dictionary, ref TKey key)
+ {
+ if (dictionary.buckets != null)
+ {
+ var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
+
+ var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ while (bucket >= 0)
+ {
+ ref var entry = ref dictionary.entries[bucket];
+ if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key))
+ return entry.value;
+
+ bucket = entry.next;
+ }
+ }
+ return default;
+ }
+
+ [return: MaybeNull]
+ public static TValue TryGetValue(this Dictionary dictionary, TKey key)
+ {
+ if (dictionary.buckets != null)
+ {
+ var keyCode = HashCode.Get(key) & 0x7FFFFFFF;
+
+ var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ while (bucket >= 0)
+ {
+ ref var entry = ref dictionary.entries[bucket];
+ if (entry.hashCode == keyCode && entry.key.Equals(key))
+ return entry.value;
+
+ bucket = entry.next;
+ }
+ }
+ return default;
+ }
+
+ [DoesNotReturn]
+ private static ref V ThrowKeyNotFoundException(T key)
+ {
+ Guard.IsNotNull(key);
+ ThrowKeyNotFoundException(key);
+ return ref Unsafe.NullRef();
+ }
+
+ [DoesNotReturn]
+ private static void ThrowKeyNotFoundException(object key)
+ => throw new KeyNotFoundException(
+ string.Format("The given key '{0}' was not present in the dictionary.", key));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref int Version(this List list) => ref list._version;
+
+ ///
+ /// Returns a list's internal _items array. Keep in mind list.Version() needs to change every time items are modified.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T[] ItemsUnchecked(this List list) => list._items;
+
+ ///
+ /// Returns a span pointing to a list's internal _items array. Keep in mind list.Version() needs to change every time items are modified.
+ ///
+ public static Span AsSpanUnchecked(this List list) => new(list._items, 0, list.Count);
+
+ public static ReadOnlySpan AsReadOnlySpan(this List list) => new(list._items, 0, list.Count);
+
+ public static void ReplaceContentsWith(this List list, V collection) where V : ICollection
+ {
+ if (ReferenceEquals(list, collection))
+ return;
+
+ if (collection is null
+ || (collection.Count is var collectionCount
+ && collectionCount == 0))
+ {
+ list.Clear();
+ return;
+ }
+
+ if (collectionCount <= list.Count)
+ {
+ if (collectionCount < list.Count)
+ list.RemoveRange(collectionCount, list.Count - collectionCount);
+ collection.CopyTo(list._items, 0);
+ }
+ else
+ {
+ list._items = new T[collectionCount];
+ collection.CopyTo(list._items, 0);
+ list._size = collectionCount;
+ }
+ list._version++;
+ }
+
+ public static IList TryNew(IEnumerable enumerable, Type type) => GetListExtensionMethods(type).New(enumerable);
+
+ public static void TryAddRange(this IList list, T collection, Type type) where T : ICollection => GetListExtensionMethods(type).AddRange(list, collection);
+
+ public static Array TryToArray(this IList list, Type type) => GetListExtensionMethods(type).ToArray(list);
+
+ public static GenericBase GetListExtensionMethods(Type type)
+ => ListExtensionCache.TryGetValue(type)
+ ?? CreateListExtensionMethods(type);
+
+ private static GenericBase CreateListExtensionMethods(Type type)
+ {
+ var methods = (GenericBase)Activator.CreateInstance(typeof(Generic<>).MakeGenericType(type));
+ ListExtensionCache[type] = methods;
+ return methods;
+ }
+
+ private static Dictionary ListExtensionCache => _listExtensionCache ??= new();
+
+ [ThreadStatic]
+ private static Dictionary? _listExtensionCache;
+
+ public abstract class GenericBase
+ {
+ public abstract void AddRange(IList list, ICollection collection);
+ public abstract Array ToArray(IList list);
+ public abstract IList New(IEnumerable enumerable);
+ }
+
+ public class Generic : GenericBase
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void AddRange(IList list, ICollection collection) => ((List)list).AddRange((IEnumerable)collection);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override Array ToArray(IList list) => ((List)list).ToArray();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override IList New(IEnumerable enumerable) => new List((IEnumerable)enumerable);
+ }
+}
\ No newline at end of file
diff --git a/Source/FisheryLib/Convert.cs b/Source/FisheryLib/Convert.cs
new file mode 100644
index 0000000..40c8fe8
--- /dev/null
+++ b/Source/FisheryLib/Convert.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2022 bradson
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+using System.Globalization;
+using System.Runtime.CompilerServices;
+
+namespace FisheryLib;
+public static class Convert
+{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe TTo To(TFrom from) where TFrom : IConvertible
+ => FunctionPointers.Convert.To.Default(from, CultureInfo.InvariantCulture);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe TTo To(TFrom from, IFormatProvider provider) where TFrom : IConvertible
+ => FunctionPointers.Convert.To.Default(from, provider);
+}
\ No newline at end of file
diff --git a/Source/FisheryLib/Diagnostics/Attributes/CallerArgumentExpressionAttribute.cs b/Source/FisheryLib/Diagnostics/Attributes/CallerArgumentExpressionAttribute.cs
new file mode 100644
index 0000000..ba668ba
--- /dev/null
+++ b/Source/FisheryLib/Diagnostics/Attributes/CallerArgumentExpressionAttribute.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.CompilerServices;
+
+///
+/// An attribute that allows parameters to receive the expression of other parameters.
+///
+/// Internal copy from the BCL attribute.
+[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
+internal sealed class CallerArgumentExpressionAttribute : Attribute
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The condition parameter value.
+ public CallerArgumentExpressionAttribute(string parameterName) => ParameterName = parameterName;
+
+ ///
+ /// Gets the parameter name the expression is retrieved from.
+ ///
+ public string ParameterName { get; }
+}
\ No newline at end of file
diff --git a/Source/FisheryLib/Diagnostics/Extensions/TypeExtensions.cs b/Source/FisheryLib/Diagnostics/Extensions/TypeExtensions.cs
new file mode 100644
index 0000000..aacaa23
--- /dev/null
+++ b/Source/FisheryLib/Diagnostics/Extensions/TypeExtensions.cs
@@ -0,0 +1,162 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace FisheryLib.Utility.Diagnostics;
+
+///
+/// Helpers for working with types.
+///
+public static class TypeExtensions
+{
+ ///
+ /// The mapping of built-in types to their simple representation.
+ ///
+ private static readonly IReadOnlyDictionary BuiltInTypesMap = new Dictionary
+ {
+ [typeof(bool)] = "bool",
+ [typeof(byte)] = "byte",
+ [typeof(sbyte)] = "sbyte",
+ [typeof(short)] = "short",
+ [typeof(ushort)] = "ushort",
+ [typeof(char)] = "char",
+ [typeof(int)] = "int",
+ [typeof(uint)] = "uint",
+ [typeof(float)] = "float",
+ [typeof(long)] = "long",
+ [typeof(ulong)] = "ulong",
+ [typeof(double)] = "double",
+ [typeof(decimal)] = "decimal",
+ [typeof(object)] = "object",
+ [typeof(string)] = "string",
+ [typeof(void)] = "void"
+ };
+
+ ///
+ /// A thread-safe mapping of precomputed string representation of types.
+ ///
+ private static readonly ConditionalWeakTable DisplayNames = new();
+
+ ///
+ /// Returns a simple string representation of a type.
+ ///
+ /// The input type.
+ /// The string representation of .
+ public static string ToTypeString(this Type type)
+ {
+ // Local function to create the formatted string for a given type
+ static string FormatDisplayString(Type type, int genericTypeOffset, ReadOnlySpan typeArguments)
+ {
+ // Primitive types use the keyword name
+ if (BuiltInTypesMap.TryGetValue(type, out var typeName))
+ {
+ return typeName!;
+ }
+
+ // Array types are displayed as Foo[]
+ if (type.IsArray)
+ {
+ var elementType = type.GetElementType()!;
+ var rank = type.GetArrayRank();
+
+ return $"{FormatDisplayString(elementType, 0, elementType.GetGenericArguments())}[{new string(',', rank - 1)}]";
+ }
+
+ // By checking generic types here we are only interested in specific cases,
+ // ie. nullable value types or value tuples. We have a separate path for custom
+ // generic types, as we can't rely on this API in that case, as it doesn't show
+ // a difference between nested types that are themselves generic, or nested simple
+ // types from a generic declaring type. To deal with that, we need to manually track
+ // the offset within the array of generic arguments for the whole constructed type.
+ if (type.IsGenericType)
+ {
+ var genericTypeDefinition = type.GetGenericTypeDefinition();
+
+ // Nullable types are displayed as T?
+ if (genericTypeDefinition == typeof(Nullable<>))
+ {
+ var nullableArguments = type.GetGenericArguments();
+
+ return $"{FormatDisplayString(nullableArguments[0], 0, nullableArguments)}?";
+ }
+
+ // ValueTuple types are displayed as (T1, T2)
+ if (genericTypeDefinition == typeof(ValueTuple<>) ||
+ genericTypeDefinition == typeof(ValueTuple<,>) ||
+ genericTypeDefinition == typeof(ValueTuple<,,>) ||
+ genericTypeDefinition == typeof(ValueTuple<,,,>) ||
+ genericTypeDefinition == typeof(ValueTuple<,,,,>) ||
+ genericTypeDefinition == typeof(ValueTuple<,,,,,>) ||
+ genericTypeDefinition == typeof(ValueTuple<,,,,,,>) ||
+ genericTypeDefinition == typeof(ValueTuple<,,,,,,,>))
+ {
+ var formattedTypes = type.GetGenericArguments().Select(t => FormatDisplayString(t, 0, t.GetGenericArguments()));
+
+ return $"({string.Join(", ", formattedTypes)})";
+ }
+ }
+
+ string displayName;
+
+ // Generic types
+ if (type.Name.Contains('`'))
+ {
+ // Retrieve the current generic arguments for the current type (leaf or not)
+ var tokens = type.Name.Split('`');
+ var genericArgumentsCount = int.Parse(tokens[1]);
+ var typeArgumentsOffset = typeArguments.Length - genericTypeOffset - genericArgumentsCount;
+ var currentTypeArguments = typeArguments.Slice(typeArgumentsOffset, genericArgumentsCount).ToArray();
+ var formattedTypes = currentTypeArguments.Select(t => FormatDisplayString(t, 0, t.GetGenericArguments()));
+
+ // Standard generic types are displayed as Foo
+ displayName = $"{tokens[0]}<{string.Join(", ", formattedTypes)}>";
+
+ // Track the current offset for the shared generic arguments list
+ genericTypeOffset += genericArgumentsCount;
+ }
+ else
+ {
+ // Simple custom types
+ displayName = type.Name;
+ }
+
+ // If the type is nested, recursively format the hierarchy as well
+ return type.IsNested
+ ? $"{FormatDisplayString(type.DeclaringType!, genericTypeOffset, typeArguments)}.{displayName}"
+ : $"{type.Namespace}.{displayName}";
+ }
+
+ // Atomically get or build the display string for the current type.
+ return DisplayNames.GetValue(type, t =>
+ {
+ // By-ref types are displayed as T&
+ if (t.IsByRef)
+ {
+ t = t.GetElementType()!;
+
+ return $"{FormatDisplayString(t, 0, t.GetGenericArguments())}&";
+ }
+
+ // Pointer types are displayed as T*
+ if (t.IsPointer)
+ {
+ var depth = 0;
+
+ // Calculate the pointer indirection level
+ while (t.IsPointer)
+ {
+ depth++;
+ t = t.GetElementType()!;
+ }
+
+ return $"{FormatDisplayString(t, 0, t.GetGenericArguments())}{new string('*', depth)}";
+ }
+
+ // Standard path for concrete types
+ return FormatDisplayString(t, 0, t.GetGenericArguments());
+ });
+ }
+}
diff --git a/Source/FisheryLib/Diagnostics/Extensions/ValueTypeExtensions.cs b/Source/FisheryLib/Diagnostics/Extensions/ValueTypeExtensions.cs
new file mode 100644
index 0000000..54511d9
--- /dev/null
+++ b/Source/FisheryLib/Diagnostics/Extensions/ValueTypeExtensions.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+namespace FisheryLib.Utility.Diagnostics;
+
+///
+/// Helpers for working with value types.
+///
+public static class ValueTypeExtensions
+{
+ ///
+ /// Gets the table of hex characters (doesn't allocate, maps to .text section, see ).
+ ///
+ private static ReadOnlySpan HexCharactersTable => new[]
+ {
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3',
+ (byte)'4', (byte)'5', (byte)'6', (byte)'7',
+ (byte)'8', (byte)'9', (byte)'A', (byte)'B',
+ (byte)'C', (byte)'D', (byte)'E', (byte)'F'
+ };
+
+ ///
+ /// Returns a hexadecimal representation of a given value, left-padded and ordered as big-endian.
+ ///
+ /// The input type to format to .
+ /// The input value to format to .
+ ///
+ /// The hexadecimal representation of (with the '0x' prefix), left-padded to byte boundaries and ordered as big-endian.
+ ///
+ ///
+ /// As a byte (8 bits) is represented by two hexadecimal digits (each representing a group of 4 bytes), each
+ /// representation will always contain an even number of digits. For instance:
+ ///
+ /// Console.WriteLine(1.ToHexString()); // "0x01"
+ /// Console.WriteLine(((byte)255).ToHexString()); // "0xFF"
+ /// Console.WriteLine((-1).ToHexString()); // "0xFFFFFFFF"
+ ///
+ ///
+ public static unsafe string ToHexString(this T value)
+ where T : unmanaged
+ {
+ var sizeOfT = Unsafe.SizeOf();
+ var bufferSize = (2 * sizeOfT) + 2;
+ var p = stackalloc char[bufferSize];
+
+ p[0] = '0';
+ p[1] = 'x';
+
+ ref var rh = ref HexCharactersTable.DangerousGetPinnableReference();
+
+ for (int i = 0, j = bufferSize - 2; i < sizeOfT; i++, j -= 2)
+ {
+ var b = ((byte*)&value)[i];
+ var low = b & 0x0F;
+ var high = (b & 0xF0) >> 4;
+
+ p[j + 1] = (char)Unsafe.Add(ref rh, low);
+ p[j] = (char)Unsafe.Add(ref rh, high);
+ }
+
+ return new(p, 0, bufferSize);
+ }
+}
diff --git a/Source/FisheryLib/Diagnostics/Generated/Guard.Collection.g.cs b/Source/FisheryLib/Diagnostics/Generated/Guard.Collection.g.cs
new file mode 100644
index 0000000..cab243c
--- /dev/null
+++ b/Source/FisheryLib/Diagnostics/Generated/Guard.Collection.g.cs
@@ -0,0 +1,1822 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+// =====================
+// Auto generated file
+// =====================
+
+using System.Runtime.CompilerServices;
+
+namespace FisheryLib.Utility.Diagnostics;
+
+///
+partial class Guard
+{
+ ///
+ /// Asserts that the input instance must be empty.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is != 0.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IsEmpty(Span span, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length == 0)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForIsEmpty(span, name);
+ }
+
+ ///
+ /// Asserts that the input instance must not be empty.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is == 0.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IsNotEmpty(Span span, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length != 0)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForIsNotEmptyWithSpan(name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size of a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is != .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeEqualTo(Span span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length == size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(span, size, name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size not equal to a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is == .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeNotEqualTo(Span span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length != size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeNotEqualTo(span, size, name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size over a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is <= .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeGreaterThan(Span span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length > size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThan(span, size, name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size of at least or equal to a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is < .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeGreaterThanOrEqualTo(Span span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length >= size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(span, size, name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size of less than a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is >= .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeLessThan(Span span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length < size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeLessThan(span, size, name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size of less than or equal to a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is > .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeLessThanOrEqualTo(Span span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length <= size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(span, size, name);
+ }
+
+ ///
+ /// Asserts that the source instance must have the same size of a destination instance.
+ ///
+ /// The item of items in the input instance.
+ /// The source instance to check the size for.
+ /// The destination instance to check the size for.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is != the one of .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeEqualTo(Span source, Span destination, [CallerArgumentExpression("source")] string name = "")
+ {
+ if (source.Length == destination.Length)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(source, destination, name);
+ }
+
+ ///
+ /// Asserts that the source instance must have a size of less than or equal to that of a destination instance.
+ ///
+ /// The item of items in the input instance.
+ /// The source instance to check the size for.
+ /// The destination instance to check the size for.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is > the one of .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeLessThanOrEqualTo(Span source, Span destination, [CallerArgumentExpression("source")] string name = "")
+ {
+ if (source.Length <= destination.Length)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(source, destination, name);
+ }
+
+ ///
+ /// Asserts that the input index is valid for a given instance.
+ ///
+ /// The item of items in the input instance.
+ /// The input index to be used to access .
+ /// The input instance to use to validate .
+ /// The name of the input parameter being tested.
+ /// Thrown if is not valid to access .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IsInRangeFor(int index, Span span, [CallerArgumentExpression("index")] string name = "")
+ {
+ if ((uint)index < (uint)span.Length)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRangeFor(index, span, name);
+ }
+
+ ///
+ /// Asserts that the input index is not valid for a given instance.
+ ///
+ /// The item of items in the input instance.
+ /// The input index to be used to access .
+ /// The input instance to use to validate .
+ /// The name of the input parameter being tested.
+ /// Thrown if is valid to access .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IsNotInRangeFor(int index, Span span, [CallerArgumentExpression("index")] string name = "")
+ {
+ if ((uint)index >= (uint)span.Length)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(index, span, name);
+ }
+
+ ///
+ /// Asserts that the input instance must be empty.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is != 0.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IsEmpty(ReadOnlySpan span, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length == 0)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForIsEmpty(span, name);
+ }
+
+ ///
+ /// Asserts that the input instance must not be empty.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is == 0.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IsNotEmpty(ReadOnlySpan span, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length != 0)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForIsNotEmptyWithReadOnlySpan(name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size of a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is != .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length == size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(span, size, name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size not equal to a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is == .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeNotEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length != size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeNotEqualTo(span, size, name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size over a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is <= .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeGreaterThan(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length > size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThan(span, size, name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size of at least or equal to a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is < .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeGreaterThanOrEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length >= size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(span, size, name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size of less than a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is >= .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeLessThan(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length < size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeLessThan(span, size, name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size of less than or equal to a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is > .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeLessThanOrEqualTo(ReadOnlySpan span, int size, [CallerArgumentExpression("span")] string name = "")
+ {
+ if (span.Length <= size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(span, size, name);
+ }
+
+ ///
+ /// Asserts that the source instance must have the same size of a destination instance.
+ ///
+ /// The item of items in the input instance.
+ /// The source instance to check the size for.
+ /// The destination instance to check the size for.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is != the one of .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeEqualTo(ReadOnlySpan source, Span destination, [CallerArgumentExpression("source")] string name = "")
+ {
+ if (source.Length == destination.Length)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(source, destination, name);
+ }
+
+ ///
+ /// Asserts that the source instance must have a size of less than or equal to that of a destination instance.
+ ///
+ /// The item of items in the input instance.
+ /// The source instance to check the size for.
+ /// The destination instance to check the size for.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is > the one of .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeLessThanOrEqualTo(ReadOnlySpan source, Span destination, [CallerArgumentExpression("source")] string name = "")
+ {
+ if (source.Length <= destination.Length)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(source, destination, name);
+ }
+
+ ///
+ /// Asserts that the input index is valid for a given instance.
+ ///
+ /// The item of items in the input instance.
+ /// The input index to be used to access .
+ /// The input instance to use to validate .
+ /// The name of the input parameter being tested.
+ /// Thrown if is not valid to access .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IsInRangeFor(int index, ReadOnlySpan span, [CallerArgumentExpression("index")] string name = "")
+ {
+ if ((uint)index < (uint)span.Length)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRangeFor(index, span, name);
+ }
+
+ ///
+ /// Asserts that the input index is not valid for a given instance.
+ ///
+ /// The item of items in the input instance.
+ /// The input index to be used to access .
+ /// The input instance to use to validate .
+ /// The name of the input parameter being tested.
+ /// Thrown if is valid to access .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IsNotInRangeFor(int index, ReadOnlySpan span, [CallerArgumentExpression("index")] string name = "")
+ {
+ if ((uint)index >= (uint)span.Length)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(index, span, name);
+ }
+
+ ///
+ /// Asserts that the input instance must be empty.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is != 0.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IsEmpty(Memory memory, [CallerArgumentExpression("memory")] string name = "")
+ {
+ if (memory.Length == 0)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForIsEmpty(memory, name);
+ }
+
+ ///
+ /// Asserts that the input instance must not be empty.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is == 0.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void IsNotEmpty(Memory memory, [CallerArgumentExpression("memory")] string name = "")
+ {
+ if (memory.Length != 0)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForIsNotEmpty>(name);
+ }
+
+ ///
+ /// Asserts that the input instance must have a size of a specified value.
+ ///
+ /// The item of items in the input instance.
+ /// The input instance to check the size for.
+ /// The target size to test.
+ /// The name of the input parameter being tested.
+ /// Thrown if the size of is != .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void HasSizeEqualTo(Memory memory, int size, [CallerArgumentExpression("memory")] string name = "")
+ {
+ if (memory.Length == size)
+ {
+ return;
+ }
+
+ ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(memory, size, name);
+ }
+
+ ///
+ /// Asserts that the input