Commit graph

18 commits

Author SHA1 Message Date
Kamron Batman
79f736aa83
fix: keep the fallback localization table registered when emptied (#2724)
## Summary
`Localization.Remove` drops a language's dictionary from `_localizations` once its last entry is removed. For the fallback language (`enu`), `_fallbackEntries` still pointed at the dropped dictionary. A later `Add("enu", ...)` then created a new dictionary, and because `_fallbackEntries` was not null, `Add`'s `??=` left it on the old one. Every fallback lookup missed the new entries.

The fallback table now stays registered when it empties. Later adds land in the dictionary lookups read, and removed entries stay removed. Nulling `_fallbackEntries` instead would have made the next lookup reload `cliloc.enu` from disk and bring back the removed entries.

Nothing in production calls `Remove` today. It is public API for shards and tests; #2722 adds a test cleanup that removes entries.

## Tests
- `LocalizationEntryTests.RemovingTheLastFallbackEntry_KeepsLaterAddsVisible` clears the tables, adds then removes one `enu` entry, adds another, and checks that the fallback lookup finds the new entry and not the removed one. It clears again afterwards so later tests reload the real tables. It fails without the fix.
- Server.Tests: all pass.
2026-10-18 20:10:39 -07:00
Kamron Batman
61e41df00c
feat: Add zero-alloc interpolation handler to ValueStringBuilder, replace all StringBuilder usage (#2387)
## Summary

- **Add a self-referencing `InterpolationHandler` to `ValueStringBuilder`** that writes directly into the builder's buffer — zero intermediate allocation, works with `stackalloc`-backed builders
- **Replace all `System.Text.StringBuilder` usage** across the codebase with `ValueStringBuilder`
- **Convert `ValueStringBuilder.Create()` to `stackalloc`** at 10 sites where output length is provably bounded
- **Convert manual `Dispose()` to `using var`** where possible, and hoist loop-scoped builders outside loops with `Reset()`
- **Convert verbose `Append()` chains to `Append($"...")`** interpolation for readability
- **Add comprehensive documentation** for string handling patterns

## InterpolationHandler Design

`ValueStringBuilder` is a `ref struct`, which creates challenges for C#'s interpolated string handler pattern:

- **`ref` fields to ref structs are not allowed** (CS9050)
- **`[InterpolatedStringHandlerArgument("")]` passes struct receivers by value**, not by ref
- **`ISelfInterpolatedStringHandler` requires boxing** ref structs into interface fields

**Solution: Copy-and-reconcile pattern.** The handler receives a value copy of the builder. The copy shares the same underlying `char` buffer (`Span` points to the same `stackalloc`/pooled memory), so writes go to the original buffer. `Append()` reconciles by `this = handler._builder`, updating `_length` and any buffer references changed by `Grow()`.

This is safe because:
- The game loop is single-threaded — no concurrent access between handler construction and reconciliation
- If `Grow()` occurs in the copy, the original's stale buffer isn't accessed until `Append()` replaces it
- `Dispose()` correctly returns the reconciled buffer to the pool

## Changes by Category

### ValueStringBuilder (`Projects/Server/Buffers/ValueStringBuilder.cs`)
- Added nested `InterpolationHandler` ref struct with copy-and-reconcile pattern
- Added `Append([InterpolatedStringHandlerArgument("")] scoped ref InterpolationHandler)` method
- Removed `RawInterpolatedStringHandler` overloads (new handler replaces them)
- All `AppendFormatted` overloads delegate to existing `Append` methods (no code duplication)
- Alignment support via direct private field access (nested type privilege)

### StringBuilder → ValueStringBuilder (15 files)
Replaced all `new StringBuilder()` with `ValueStringBuilder.Create()` or `stackalloc`:
- ConPVP games: KingOfTheHill, DoubleDom, CTF, BombingRun, TourneyMatch
- ConPVP infrastructure: Tournament, Participant, TourneyParticipant
- ConPVP gumps: ArenaGump, TournamentBracketGump, AcceptTeamGump, ConfirmSignupGump
- Commands: Handlers, Logging, Add
- Other: TownCrier, SpeechLogGump, TestCenter

Key patterns:
- `sb = new StringBuilder()` reassignment → `sb.Reset()`
- `sb.AppendFormat("{0:N0}", value)` → `sb.Append($"{value:N0}")`
- `sb.Append(x).Append(y)` chains → separate statements (VSB returns void)

### Create() → stackalloc (10 files)
Converted heap-allocated builders to stackalloc where output is bounded:
- ClientVersion (32), MapSelection (160), HouseRaffleStone (48)
- HolySense (96), UnholySense (96), ClientVerification (192)
- AcceptTeamGump (64), ConfirmSignupGump (64)
- BaseWeapon (160), BaseArmor (128)

### Loop optimizations (2 files)
Hoisted `ValueStringBuilder` creation outside loops with `Reset()` per iteration:
- TourneyMatch.cs: `using var` inside for loop → stackalloc before loop
- ArenaGump.cs: `Create()` + `Dispose()` per iteration → stackalloc before loop

### Append chain → interpolation (5 files)
Converted multi-line `Append()` chains to `Append($"...")`:
- BountyMessage.cs: title switch (6 cases), paragraph (15→1 Append), description lines, closing
- AcceptTeamGump, ConfirmSignupGump, TournamentBracketGump: tournament type strings
- AdminGump: comment/tag formatting in loops

### Documentation
- `dev-docs/string-handling.md`: Full reference — construction, interpolation, disposal, decision guide
- `dev-docs/claude-skills/modernuo-string-handling.md`: Claude skill with quick reference
- `CLAUDE.md`: Added rule 17 (no StringBuilder), dev-docs table entry, skills table entry
- `dev-docs/code-standards.md`: Updated memory management section

## Test Plan

- [x] `dotnet build` — 0 errors, 0 warnings
- [x] `dotnet test` — 940/940 tests pass
- [x] 28 ValueStringBuilder tests covering all reconciliation scenarios:
  - Stackalloc no-grow, stackalloc with grow (→pool transition)
  - Heap no-grow, heap with grow, heap double grow
  - Pre-existing content with and without grow
  - Sequential multiple `Append($"...")` calls
  - Mixed plain + interpolated Append
  - Empty interpolation, literal-only, format specifiers
  - Null string holes, ISpanFormattable types
  - Dispose after stackalloc→pool grow
2026-03-22 14:23:44 -07:00
Kamron Batman
e1e1a7c640
fix: Bumps deps. Updates copyrights (#2353) 2026-03-05 19:36:54 -08:00
Kamron Batman
ebaf104935
chore: Use var everywhere (#2294) 2025-12-27 16:47:28 -08:00
Kamron Batman
c75514cc04
feat: Updates to .NET 9 (#1984)
### Summary

* Bumps to .NET 9 with updated dependencies
* Comparing a value type against null is no longer allowed
* CI/CD now uses the version specified in global.json
* Serialization generator updated to .NET 9 with bug fixes, fixes to turkish language, and parallelization
2024-12-08 10:16:34 -08:00
Kamron Batman
ac06a0d52d
fix: Adds BWT cliloc support for v7.0.104+ (#1982) 2024-10-19 22:01:35 -07:00
Kamron Batman
6cb18f87fa
fix: Simplifies create food and localizes. Fixes localization fallback (#1721) 2024-04-05 19:45:42 -07:00
Kamron Batman
b5342a82e1
fix: Fixes language casing and adds localization reset command (#1703) 2024-03-09 11:02:56 -08:00
Kamron Batman
8389bfacfe
chore: Updates copyright (#1448) 2023-08-09 09:09:26 -07:00
mdodkins
a9a2a89908
feat: Customize expansion and set maps on first boot (#1425) 2023-07-31 20:46:49 -07:00
Kamron Batman
f24c6a08dd
fix: Fixes string interpolation in value string builder (#1339) 2023-02-13 23:09:50 -08:00
Kamron Batman
2fb660040b
fix: Cleans up namespaces (#1069) 2022-06-15 19:52:23 -07:00
Kamron Batman
5f00330a66
fix: Adds optimized formatter for clilocs (#1045)
Adds an optimized formatter for localization. Example:
```cs
string localizationText = Localization.Format(1050039, "enu", $"{m_Amount}\t{LabelNumber:#}");
```

Fixes #1044
2022-06-04 11:53:32 -07:00
Kamron Batman
87b63b38a5
fix: Eliminates string allocations while writing gump packets (#1017)
* Introduces `RawInterpolatedStringHandler` which is exactly the same as `DefaultInterpolatedStringHandler` except it _unsafely exposes_ it's `ReadOnlySpan<char>` buffer. This is useful for writing the string's data without actually building the string.
* Uses this new string interpolation handler in `SpanWriter` to eliminate intermediate strings built. This is immensely useful in eliminating string allocations in writing Gump packets.
2022-05-10 18:50:23 -07:00
Kamron Batman
097c0143b0
fix: Fixes missing localization interpolation handler (#1016) 2022-05-10 00:42:59 -07:00
Kamron Batman
8031de4738
fix: Fixes string format of cliloc arguments (#1015) 2022-05-10 00:37:07 -07:00
Kamron Batman
47d427ed6e
fix: Adds cliloc replacement support (#1014) 2022-05-09 21:10:58 -07:00
Kamron Batman
be7da3a3dc
feat: Adds cliloc support and fixes valuestringbuilder ctor (#1013)
- [X] Adds cliloc support using the following API:
```cs
public static class Localization
{
    string GetText(int number);
    string GetText(int number, string lang);
    string Format(int number, params object[] args);
    string Format(int number, string lang, params object[] args);

    string Format(int number, $"{arg1}{arg2}{arg3}");
    string Format(int number, lang, $"{arg1}{arg2}{arg3}");

    bool TryGetLocalization(int number, out LocalizationEntry entry);
    bool TryGetLocalization(int number, string lang, out LocalizationEntry entry);
}

public class LocalizationEntry
{
    string Language { get; }
    string Number { get; }
    string Text { get; }
    string?[] TextSlices { get; } // Used for string building
    string StringFormatter { get; }

    string Format(params object[] args);
    string Format($"{arg1}{arg2}{arg3}");
}
```

- [X] Fixes a bug with ValueStringBuilder and default initialization size
- [X] Optimizes ValueStringBuilder to use `ISpanFormattable`
- [X] Adds `Append<T>(T value);` support ValueStringBuilder
- [X] Adds `Append($"");` string interpolation support to ValueStringBuider
2022-05-08 21:24:32 -07:00