acdream/src/AcDream.App/UI/Layout/DatStringResolver.cs
Erik 749e8ceeb1 fix(rendering): bound portal resource lifetime
Separate logical ownership, render publication, and GPU retirement across live entities, landblocks, particles, textures, mesh arenas, portal/UI teardown, and per-frame scratch storage. Add bounded DAT/texture caches, upload budgets, three-frame fence retirement, exact-incarnation appearance reconciliation, frame pacing, and extensive lifetime conformance coverage.\n\nThe seven-destination connected route now cuts peak working/private memory roughly in half, returns Caul to 125-153 FPS locally, and produces no WER or AMD reset.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
2026-07-18 21:35:16 +02:00

83 lines
2.7 KiB
C#

using DatReaderWriter;
using AcDream.Content;
using DatReaderWriter.DBObjs;
namespace AcDream.App.UI.Layout;
/// <summary>
/// Resolves retail <c>StringInfo</c> values through local.dat string tables.
/// The caller owns synchronization around <see cref="DatCollection"/> reads.
/// </summary>
/// <remarks>
/// Retail reference: <c>StringInfo::GetString</c> and
/// <c>compute_str_hash @ 0x00413110</c>. A StringInfo's token selects one
/// localized string variant; ordinary UI labels use token zero.
/// </remarks>
public sealed class DatStringResolver
{
private readonly IDatReaderWriter _dats;
private readonly Dictionary<uint, StringTable?> _tables = new();
public DatStringResolver(IDatReaderWriter dats)
=> _dats = dats ?? throw new ArgumentNullException(nameof(dats));
public string? Resolve(UiStringInfoValue info)
=> Resolve(info.TableId, info.StringId, info.Token);
public string? Resolve(uint tableId, uint stringId, int token = 0)
{
if (tableId == 0u || stringId == 0u)
return null;
if (!_tables.TryGetValue(tableId, out StringTable? table))
{
table = _dats.Get<StringTable>(tableId);
_tables[tableId] = table;
}
if (table is null
|| !table.Strings.TryGetValue(stringId, out var entry)
|| entry.Strings.Count == 0)
return null;
int index = token >= 0 && token < entry.Strings.Count ? token : 0;
return entry.Strings[index].Value;
}
/// <summary>Returns every literal token for one retail StringInfo entry.</summary>
public string[]? ResolveAll(uint tableId, uint stringId)
{
if (tableId == 0u || stringId == 0u)
return null;
if (!_tables.TryGetValue(tableId, out StringTable? table))
{
table = _dats.Get<StringTable>(tableId);
_tables[tableId] = table;
}
return table is not null
&& table.Strings.TryGetValue(stringId, out var entry)
&& entry.Strings.Count != 0
? entry.Strings.Select(value => value.Value).ToArray()
: null;
}
/// <summary>
/// Exact retail ELF-style string hash used for StringInfo keys.
/// Ported line-for-line from <c>compute_str_hash @ 0x00413110</c>.
/// </summary>
public static uint ComputeHash(string value)
{
ArgumentNullException.ThrowIfNull(value);
uint result = 0u;
foreach (char c in value)
{
result = unchecked((result << 4) + (byte)c);
uint high = result & 0xF0000000u;
if (high != 0u)
result = ((high >> 24) ^ result) & 0x0FFFFFFFu;
}
return result == uint.MaxValue ? uint.MaxValue - 1u : result;
}
}