Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
49d3e36
import *just* the resp-reader parts from the IO rewrite from side branch
mgravell Feb 13, 2026
e460f42
WIP; migrate read code - main parse loop untested but complete-ish
mgravell Feb 13, 2026
5f9c3f9
nearly compiles
mgravell Feb 15, 2026
c41e095
getting there
mgravell Feb 15, 2026
0b07512
working unit tests and API shim
mgravell Feb 15, 2026
9837995
WIP
mgravell Feb 16, 2026
3962f0a
deal with LoggingTunnel, untested
mgravell Feb 17, 2026
713634a
core write with basic buffer
mgravell Feb 17, 2026
379a2c8
fix integer unit tests
mgravell Feb 17, 2026
7c70b1f
start migrating processors
mgravell Feb 17, 2026
9a4255b
more processors
mgravell Feb 17, 2026
0e5f4d0
more processors
mgravell Feb 17, 2026
61ebb44
nits
mgravell Feb 17, 2026
a9f44b3
simplify readers to consider scalar/aggregate as the primary factor, …
mgravell Feb 18, 2026
dde5342
pubsub numsub processor unit test
mgravell Feb 18, 2026
5afa061
add missing test cases
mgravell Feb 18, 2026
f60b6f3
streaming string tests
mgravell Feb 18, 2026
379e4e1
streaming aggregate tests
mgravell Feb 18, 2026
de1b8bc
datetime processor
mgravell Feb 18, 2026
8707cd1
split test files
mgravell Feb 18, 2026
3bab5a1
WIP
mgravell Feb 18, 2026
f48bfec
add stateful FillAll and use stateful ReadPastArray
mgravell Feb 18, 2026
1d90cdd
scan and some stream
mgravell Feb 18, 2026
bb5a372
more done
mgravell Feb 18, 2026
8a2a7bd
lease tests/processors
mgravell Feb 18, 2026
cfa9376
latency processors
mgravell Feb 18, 2026
ba56cf9
vector-set info
mgravell Feb 19, 2026
118205a
role
mgravell Feb 19, 2026
b29a7a4
allow request/response testing of message and processors together
mgravell Feb 19, 2026
9fd546b
cleanup
mgravell Feb 19, 2026
8d196db
time/timing
mgravell Feb 19, 2026
33a5f6a
geo
mgravell Feb 19, 2026
6be93c5
refactor tests; geo
mgravell Feb 19, 2026
5be301b
include all the files
mgravell Feb 19, 2026
0a91e2b
WIP
mgravell Feb 20, 2026
ec54701
cull
mgravell Feb 20, 2026
50d09af
Test infrastructure for modules developers
mgravell Feb 21, 2026
95c3f0f
nits
mgravell Feb 21, 2026
03928ba
fast hash: char support, perf
mgravell Feb 21, 2026
dce78e4
enum parse generator
mgravell Feb 22, 2026
ba1f6c1
enum parse benchmark
mgravell Feb 22, 2026
a48bc20
more enum bench
mgravell Feb 22, 2026
885dab5
BDN side-effect fixes
mgravell Feb 22, 2026
edd5d25
case insensitive
mgravell Feb 22, 2026
9964a2c
optimize parse by removing second hash call
mgravell Feb 22, 2026
8380c9f
FastHash -> AsciiHash
mgravell Feb 22, 2026
bed0ff4
optimize CI parsing
mgravell Feb 23, 2026
09e3b44
allow enums to generate their size constants
mgravell Feb 23, 2026
9fe8162
prefer enum parsing
mgravell Feb 23, 2026
a695497
more migrations
mgravell Feb 23, 2026
f12c45a
new reader API for enums, to avoid caller stackalloc
mgravell Feb 23, 2026
319e9a7
remove RawResult
mgravell Feb 23, 2026
ff304f3
remove "arenas"
mgravell Feb 23, 2026
5e02fcb
check namespaces globally
mgravell Feb 23, 2026
70210e0
deprecate SocketManager
mgravell Feb 23, 2026
4db3add
prune dead code
mgravell Feb 23, 2026
9c35571
change RedisLiterals to use byte[] rather than string
mgravell Feb 23, 2026
1bb4ed8
half the size of RedisValue and make it more efficient at the same time
mgravell Feb 23, 2026
bdcd643
remove some redundant bits
mgravell Feb 23, 2026
05b6e9a
Marc/resp reader server (#3016)
mgravell Feb 24, 2026
a3a6fe5
infer command
mgravell Feb 24, 2026
50239c4
prefer net10
mgravell Feb 24, 2026
523b2c7
migrate hotkeys and vectorset parsers to use enums
mgravell Feb 24, 2026
4b18685
optimize key notification code
mgravell Feb 24, 2026
b5ced7a
enum fixes
mgravell Feb 24, 2026
2c6d839
PushKind
mgravell Feb 24, 2026
66f87f1
re-use PushKind parsing
mgravell Feb 24, 2026
1cfa073
unit test to show RedisCommand behaviour before CommandBytes swap
mgravell Feb 25, 2026
252986c
Drop CommandBytes (#3019)
mgravell Feb 25, 2026
ab4da89
formalize behavior of ExecuteMessage
mgravell Feb 25, 2026
e41459c
add tests for new lease-redis-value processor
mgravell Feb 25, 2026
7440038
merge
mgravell Feb 25, 2026
4392338
merge
mgravell Feb 25, 2026
b4be14e
explicitly throw in transactions for now
mgravell Feb 25, 2026
c9b67ce
RESP3
mgravell Feb 25, 2026
7c3e8d6
fix test / skip location error
mgravell Feb 26, 2026
13ec91b
merge
mgravell Feb 27, 2026
ca2700d
reapply bits missed from complex merge
mgravell Feb 27, 2026
0a024c5
sync write; new _\r\n failing test
mgravell Feb 27, 2026
473192e
fix NUL frame parsing
mgravell Feb 27, 2026
da37dcd
that also fixed transactions
mgravell Feb 27, 2026
2c70cd6
fix float parse
mgravell Feb 28, 2026
ce6e1db
movenext now advances
mgravell Feb 28, 2026
d3e2a36
docs
mgravell Feb 28, 2026
501beb1
- rename TryParseScalar
mgravell Feb 28, 2026
5d33404
nit
mgravell Feb 28, 2026
81192d8
use the function-ptr API exclusively
mgravell Feb 28, 2026
44432ba
more nul fixes
mgravell Feb 28, 2026
53cadf2
fix aggregate-reader termination
mgravell Feb 28, 2026
1ccef3e
need to consider both attribute and non-attribute scenarios
mgravell Feb 28, 2026
64b89ca
don't include UNKNOWN in the command-map
mgravell Feb 28, 2026
07da468
fix ulong/long edge condition in RedisValue
mgravell Feb 28, 2026
50b4f2c
better handling of "unknown"
mgravell Mar 1, 2026
7666235
fix pub/sub OOB detection
mgravell Mar 1, 2026
1f63814
unaligned
mgravell Mar 1, 2026
328f2aa
less nasty blitting
mgravell Mar 1, 2026
689cee0
pragma away AsciiHash usage in generated code
mgravell Mar 1, 2026
1f92fd6
fix nits
mgravell Mar 1, 2026
1947903
fix transactions internals
mgravell Mar 2, 2026
d7414d3
fix logging tunnel
mgravell Mar 2, 2026
cb63237
fix tests from command-detection side-effects
mgravell Mar 2, 2026
4b8c866
fix script-load ascii/raw snafu
mgravell Mar 2, 2026
b82317a
test fixes; value-tuples and ScriptResult loader
mgravell Mar 2, 2026
4c855c1
fix loggertests
mgravell Mar 2, 2026
7647748
fix XREADGROUP
mgravell Mar 2, 2026
82510e7
fix profiling test
mgravell Mar 2, 2026
5f0af37
- fix reconfigure detection
mgravell Mar 2, 2026
6f26ef0
make server connection simulatd failure detectable and skippable
mgravell Mar 2, 2026
54be904
missed one
mgravell Mar 2, 2026
a8e762d
include eng in Build.csproj
mgravell Mar 2, 2026
5b0450b
CI fix
mgravell Mar 2, 2026
4527b74
Windows: eng build
mgravell Mar 2, 2026
3954c59
codeql
mgravell Mar 2, 2026
d46d723
make fixtures less noisy during shutdown
mgravell Mar 2, 2026
9e6cfec
in-proc server: F+F on client connection
mgravell Mar 2, 2026
fe43010
observe ex
mgravell Mar 2, 2026
9d39ed8
Make format bot happy
mgravell Mar 2, 2026
d89df21
grrrr
mgravell Mar 2, 2026
4e5f25f
grrrr
mgravell Mar 2, 2026
587cb23
double flush
mgravell Mar 3, 2026
9991395
treat read-cancel like EOF
mgravell Mar 3, 2026
9b25e60
- more "supports simulate failure" checks
mgravell Mar 3, 2026
ff1b587
wip (#3024)
mgravell Mar 3, 2026
0ea65f2
3.0
mgravell Mar 3, 2026
340a4dc
Merge branch 'marc/resp-reader' of github.com:StackExchange/StackExch…
mgravell Mar 3, 2026
2c215c0
reinstate output buffer
mgravell Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ jobs:
working-directory: ./tests/RedisConfigs
run: docker compose -f docker-compose.yml up -d --wait
- name: Install .NET SDK
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
10.0.x
- name: .NET Build (eng prebuild)
run: dotnet build eng/StackExchange.Redis.Build/StackExchange.Redis.Build.csproj -c Release /p:CI=true
- name: .NET Build
run: dotnet build Build.csproj -c Release /p:CI=true
- name: StackExchange.Redis.Tests
Expand Down Expand Up @@ -132,6 +134,8 @@ jobs:
redis-cli -p 26381 INFO SERVER | grep redis_version || echo "Failed to get version for port 26381"
continue-on-error: true

- name: .NET Build (eng prebuild)
run: dotnet build eng/StackExchange.Redis.Build/StackExchange.Redis.Build.csproj -c Release /p:CI=true
- name: .NET Build
run: dotnet build Build.csproj -c Release /p:CI=true
- name: StackExchange.Redis.Tests
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
dotnet-version: |
8.0.x
10.0.x

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
Expand All @@ -52,6 +54,10 @@ jobs:
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality

- if: matrix.language == 'csharp'
name: .NET Build (eng prebuild)
run: dotnet build eng/StackExchange.Redis.Build/StackExchange.Redis.Build.csproj -c Release /p:CI=true

- if: matrix.language == 'csharp'
name: .NET Build
run: dotnet build Build.csproj -c Release /p:CI=true
Expand Down
1 change: 1 addition & 0 deletions Build.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.Build.Traversal/3.0.2">
<ItemGroup>
<ProjectReference Include="eng\**\*.csproj" />
<ProjectReference Include="src\**\*.csproj" />
<ProjectReference Include="tests\**\*.csproj" />
<ProjectReference Include="toys\**\*.csproj" />
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CodeAnalysisRuleset>$(MSBuildThisFileDirectory)Shared.ruleset</CodeAnalysisRuleset>
<MSBuildWarningsAsMessages>NETSDK1069</MSBuildWarningsAsMessages>
<NoWarn>$(NoWarn);NU5105;NU1507;SER001;SER002;SER003</NoWarn>
<NoWarn>$(NoWarn);NU5105;NU1507;SER001;SER002;SER003;SER004;SER005</NoWarn>
<PackageReleaseNotes>https://stackexchange.github.io/StackExchange.Redis/ReleaseNotes</PackageReleaseNotes>
<PackageProjectUrl>https://stackexchange.github.io/StackExchange.Redis/</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
<PackageVersion Include="System.IO.Compression" Version="4.3.0" />
<!-- note that this bumps System.Buffers, so is pinned in down-level in SE csproj -->
<PackageVersion Include="System.IO.Hashing" Version="10.0.2" />
<!-- for RESPite -->
<PackageVersion Include="System.Buffers" Version="4.6.1" />
<PackageVersion Include="System.Memory" Version="4.6.1" />

<!-- For analyzers, tied to the consumer's build SDK; at the moment, that means "us" -->
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
Expand Down
15 changes: 15 additions & 0 deletions StackExchange.Redis.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Shared.ruleset = Shared.ruleset
version.json = version.json
tests\RedisConfigs\.docker\Redis\Dockerfile = tests\RedisConfigs\.docker\Redis\Dockerfile
.github\workflows\codeql.yml = .github\workflows\codeql.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RedisConfigs", "RedisConfigs", "{96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05}"
Expand Down Expand Up @@ -127,6 +128,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eng", "eng", "{5FA0958E-6EB
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackExchange.Redis.Build", "eng\StackExchange.Redis.Build\StackExchange.Redis.Build.csproj", "{190742E1-FA50-4E36-A8C4-88AE87654340}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RESPite", "src\RESPite\RESPite.csproj", "{AEA77181-DDD2-4E43-828B-908C7460A12D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RESPite.Tests", "tests\RESPite.Tests\RESPite.Tests.csproj", "{1D324077-A15E-4EE2-9AD6-A9045636CEAC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -189,6 +194,14 @@ Global
{190742E1-FA50-4E36-A8C4-88AE87654340}.Debug|Any CPU.Build.0 = Debug|Any CPU
{190742E1-FA50-4E36-A8C4-88AE87654340}.Release|Any CPU.ActiveCfg = Release|Any CPU
{190742E1-FA50-4E36-A8C4-88AE87654340}.Release|Any CPU.Build.0 = Release|Any CPU
{AEA77181-DDD2-4E43-828B-908C7460A12D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEA77181-DDD2-4E43-828B-908C7460A12D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEA77181-DDD2-4E43-828B-908C7460A12D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEA77181-DDD2-4E43-828B-908C7460A12D}.Release|Any CPU.Build.0 = Release|Any CPU
{1D324077-A15E-4EE2-9AD6-A9045636CEAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D324077-A15E-4EE2-9AD6-A9045636CEAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D324077-A15E-4EE2-9AD6-A9045636CEAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D324077-A15E-4EE2-9AD6-A9045636CEAC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -212,6 +225,8 @@ Global
{69A0ACF2-DF1F-4F49-B554-F732DCA938A3} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A}
{59889284-FFEE-82E7-94CB-3B43E87DA6CF} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A}
{190742E1-FA50-4E36-A8C4-88AE87654340} = {5FA0958E-6EBD-45F4-808E-3447A293F96F}
{AEA77181-DDD2-4E43-828B-908C7460A12D} = {00CA0876-DA9F-44E8-B0DC-A88716BF347A}
{1D324077-A15E-4EE2-9AD6-A9045636CEAC} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {193AA352-6748-47C1-A5FC-C9AA6B5F000B}
Expand Down
3 changes: 2 additions & 1 deletion StackExchange.Redis.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=xreadgroup/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=xrevrange/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=zcard/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=zscan/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=zscan/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=zset/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
15 changes: 15 additions & 0 deletions docs/exp/SER004.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# RESPite

RESPite is an experimental library that provides high-performance low-level RESP (Redis, etc) parsing and serialization.
It is used as the IO core for StackExchange.Redis v3+. You should not (yet) use it directly unless you have a very
good reason to do so.

```xml
<NoWarn>$(NoWarn);SER004</NoWarn>
```

or more granularly / locally in C#:

``` c#
#pragma warning disable SER004
```
21 changes: 21 additions & 0 deletions docs/exp/SER005.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Unit Testing

Unit testing is great! Yay, do more of that!

This type is provided for external unit testing, in particular by people using modules or server features
not directly implemented by SE.Redis - for example to verify messsage parsing or formatting without
talking to a RESP server.

These types are considered slightly more... *mercurial*. We encourage you to use them, but *occasionally*
(not just for fun) you might need to update your test code if we tweak something. This should not impact
"real" library usage.

```xml
<NoWarn>$(NoWarn);SER005</NoWarn>
```

or more granularly / locally in C#:

``` c#
#pragma warning disable SER005
```
173 changes: 173 additions & 0 deletions eng/StackExchange.Redis.Build/AsciiHash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# AsciiHash

Efficient matching of well-known short string tokens is a high-volume scenario, for example when matching RESP literals.

The purpose of this generator is to efficiently interpret input tokens like `bin`, `f32`, etc - whether as byte or character data.

There are multiple ways of using this tool, with the main distinction being whether you are confirming a single
token, or choosing between multiple tokens (in which case an `enum` is more appropriate):

## Isolated literals (part 1)

When using individual tokens, a `static partial class` can be used to generate helpers:

``` c#
[AsciiHash] public static partial class bin { }
[AsciiHash] public static partial class f32 { }
```

Usually the token is inferred from the name; `[AsciiHash("real value")]` can be used if the token is not a valid identifier.
Underscores are replaced with hyphens, so a field called `my_token` has the default value `"my-token"`.
The generator demands *all* of `[AsciiHash] public static partial class`, and note that any *containing* types must
*also* be declared `partial`.

The output is of the form:

``` c#
static partial class bin
{
public const int Length = 3;
public const long HashCS = ...
public const long HashUC = ...
public static ReadOnlySpan<byte> U8 => @"bin"u8;
public static string Text => @"bin";
public static bool IsCS(in ReadOnlySpan<byte> value, long cs) => ...
public static bool IsCI(in RawResult value, long uc) => ...

}
```
The `CS` and `UC` are case-sensitive and case-insensitive (using upper-case) tools, respectively.

(this API is strictly an internal implementation detail, and can change at any time)

This generated code allows for fast, efficient, and safe matching of well-known tokens, for example:

``` c#
var key = ...
var hash = key.HashCS();
switch (key.Length)
{
case bin.Length when bin.Is(key, hash):
// handle bin
break;
case f32.Length when f32.Is(key, hash):
// handle f32
break;
}
```

The switch on the `Length` is optional, but recommended - these low values can often be implemented (by the compiler)
as a simple jump-table, which is very fast. However, switching on the hash itself is also valid. All hash matches
must also perform a sequence equality check - the `Is(value, hash)` convenience method validates both hash and equality.

Note that `switch` requires `const` values, hence why we use generated *types* rather than partial-properties
that emit an instance with the known values. Also, the `"..."u8` syntax emits a span which is awkward to store, but
easy to return via a property.

## Isolated literals (part 2)

In some cases, you want to be able to say "match this value, only known at runtime". For this, note that `AsciiHash`
is also a `struct` that you can create an instance of and supply to code; the best way to do this is *inside* your
`partial class`:

``` c#
[AsciiHash]
static partial class bin
{
public static readonly AsciiHash Hash = new(U8);
}
```

Now, `bin.Hash` can be supplied to a caller that takes an `AsciiHash` instance (commonly with `in` semantics),
which then has *instance* methods for case-sensitive and case-insensitive matching; the instance already knows
the target hash and payload values.

The `AsciiHash` returned implements `IEquatable<AsciiHash>` implementing case-sensitive equality; there are
also independent case-sensitive and case-insensitive comparers available via the static
`CaseSensitiveEqualityComparer` and `CaseInsensitiveEqualityComparer` properties respectively.

Comparison values can be constructed on the fly on top of transient buffers using the constructors **that take
arrays**. Note that the other constructors may allocate on a per-usage basis.

## Enum parsing (part 1)

When identifying multiple values, an `enum` may be more convenient. Consider:

``` c#
[AsciiHash]
public static partial bool TryParse(ReadOnlySpan<byte> value, out SomeEnum value);
```

This generates an efficient parser; inputs can be common `byte` or `char` types. Case sensitivity
is controlled by the optional `CaseSensitive` property on the attribute, or via a 3rd (`bool`) parameter
bbon the method, i.e.

``` c#
[AsciiHash(CaseSensitive = false)]
public static partial bool TryParse(ReadOnlySpan<byte> value, out SomeEnum value);
```

or

``` c#
[AsciiHash]
public static partial bool TryParse(ReadOnlySpan<byte> value, out SomeEnum value, bool caseSensitive = true);
```

Individual enum members can also be marked with `[AsciiHash("token value")]` to override the token payload. If
an enum member declares an empty explicit value (i.e. `[AsciiHash("")]`), then that member is ignored by the
tool; this is useful for marking "unknown" or "invalid" enum values (commonly the first enum, which by
convention has the value `0`):

``` c#
public enum SomeEnum
{
[AsciiHash("")]
Unknown,
SomeRealValue,
[AsciiHash("another-real-value")]
AnotherRealValue,
// ...
}
```

## Enum parsing (part 2)

The tool has an *additional* facility when it comes to enums; you generally don't want to have to hard-code
things like buffer-lengths into your code, but when parsing an enum, you need to know how many bytes to read.

The tool can generate a `static partial class` that contains the maximum length of any token in the enum, as well
as the maximum length of any token in bytes (when encoded as UTF-8). For example:

``` c#
[AsciiHash("SomeTypeName")]
public enum SomeEnum
{
// ...
}
```

This generates a class like the following:

``` c#
static partial class SomeTypeName
{
public const int EnumCount = 48;
public const int MaxChars = 11;
public const int MaxBytes = 11; // as UTF8
public const int BufferBytes = 16;
}
```

The last of these is probably the most useful - it allows an additional byte (to rule out false-positives),
and rounds up to word-sizes, allowing for convenient stack-allocation - for example:

``` c#
var span = reader.TryGetSpan(out var tmp) ? tmp : reader.Buffer(stackalloc byte[SomeTypeName.BufferBytes]);
if (TryParse(span, out var value))
{
// got a value
}
```

which allows for very efficient parsing of well-known tokens.
Loading
Loading