/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ShadowDictionaryEntries.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
*************************************************************************/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Server.Logging;
namespace Server;
///
/// Mirrors the field layout of Dictionary<Serial, TValue>.Entry so serialization
/// workers can scan a dictionary's backing entries array directly, in parallel, without the
/// main thread enumerating and handing off every entity.
/// The CLR's auto-layout algorithm is deterministic for identical field sequences, so this
/// struct lays out identically to the runtime's private Entry struct — and
/// proves that empirically at startup before
/// any code reads through it. If validation fails on a future runtime, callers fall back to
/// the enumerate-and-push path.
/// A free or never-used slot always has a null value (Dictionary clears values of
/// reference-type TValue on remove to release references), so occupancy is exactly
/// Value != null.
///
internal struct ShadowEntry
{
// Field order must mirror S.P.CoreLib Dictionary.Entry: hashCode, next, key, value
public uint HashCode;
public int Next;
public Serial Key;
public TValue Value;
}
internal static class ShadowDictionaryEntries
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ShadowDictionaryEntries));
///
/// True when this runtime's Dictionary entry layout matches ,
/// proven by validation at startup. All reference-type TValue instantiations share one
/// canonical layout, so a single validation covers every entity dictionary.
///
internal static readonly bool Supported = Validate();
internal static FieldInfo GetEntriesField() where TValue : class =>
typeof(Dictionary).GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance);
private sealed class ValidationValue
{
public int Id;
}
private static bool Validate()
{
try
{
var field = GetEntriesField();
if (field == null)
{
logger.Warning("Dictionary._entries not found; parallel save iteration disabled.");
return false;
}
// A compacting GC between snapshotting reference bits and scanning them can only
// produce a false negative, so retry a few times before falling back.
for (var attempt = 0; attempt < 3; attempt++)
{
if (RunValidationPass(field))
{
return true;
}
}
logger.Warning("Dictionary entry layout mismatch; parallel save iteration disabled.");
return false;
}
catch (Exception e)
{
logger.Warning(e, "Dictionary entry layout validation failed; parallel save iteration disabled.");
return false;
}
}
///
/// Measures the true element stride of the runtime's Entry struct by allocating a large
/// array of it and reading the precise allocated byte count. Verifying the stride first
/// guarantees every subsequent shadow read is in-bounds of the entries array even if the
/// field layout were wrong.
///
private static bool StrideMatches(Type entryType)
{
const int probeLength = 64 * 1024;
const int arrayHeaderSize = 24; // 64-bit: sync block + method table + length + padding
// Warm the reflection/allocation path so the measured delta contains only the probe.
Array.CreateInstance(entryType, 1);
var before = GC.GetAllocatedBytesForCurrentThread();
var probe = Array.CreateInstance(entryType, probeLength);
var delta = GC.GetAllocatedBytesForCurrentThread() - before;
GC.KeepAlive(probe);
// Alignment slack is < 8 bytes on a 512KB+ allocation, so integer division is exact.
var actualStride = (delta - arrayHeaderSize) / probeLength;
return actualStride == Unsafe.SizeOf>();
}
///
/// Proves the layout without ever materializing a managed reference through the shadow
/// view: value slots are compared as raw pointer bits (nint). Only after the stride and
/// every key and value-pointer of a churned, resized, freelist-exercised dictionary match
/// is the layout trusted for typed reads.
///
private static bool RunValidationPass(FieldInfo field)
{
const int count = 1000;
var dict = new Dictionary();
var rng = new System.Random(0x5EED);
var inserted = new List(count);
// Adds with interleaved removes and re-adds: exercises resizes and freelist reuse so
// freed slots (which production skips via null values) are present in the array.
for (var i = 0; i < count; i++)
{
var serial = (Serial)(uint)rng.Next(1, int.MaxValue);
if (dict.TryAdd(serial, new ValidationValue { Id = i }))
{
inserted.Add(serial);
}
if (i % 3 == 2)
{
var victim = inserted[rng.Next(inserted.Count)];
if (dict.Remove(victim))
{
inserted.Remove(victim);
}
}
}
if (field.GetValue(dict) is not Array entriesObj || !StrideMatches(entriesObj.GetType().GetElementType()!))
{
return false;
}
// Snapshot expected key -> value-pointer-bits pairs before scanning, so no allocation
// happens between reading the real references and reading the shadow view.
var expected = new Dictionary(dict.Count);
foreach (var (key, value) in dict)
{
var v = value;
expected[key] = Unsafe.As(ref v);
}
var entries = Unsafe.As[]>(entriesObj);
var length = entriesObj.Length;
var matched = 0;
ref var entry = ref MemoryMarshal.GetArrayDataReference(entries);
for (var i = 0; i < length; i++, entry = ref Unsafe.Add(ref entry, 1))
{
// Read the value slot as pointer bits only — never as a reference — so a wrong
// field offset cannot fabricate a managed reference for the GC to trip over.
var bits = Unsafe.As(ref entry.Value);
if (bits == 0)
{
continue; // free or never-used slot
}
if (!expected.TryGetValue(entry.Key, out var expectedBits) || bits != expectedBits)
{
return false;
}
matched++;
}
return matched == dict.Count;
}
}