using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace XFESystem.Collections; /// The pinned interpreter implements this interface after its private /// storage patch. Each engine owns its scope directly; there is no ambient thread state. public interface IWeakCollectionOwner { WeakCollectionScope WeakCollections { get; } } public sealed record WeakCollectionLifetime(int Tables, int Entries, long Created, long Disposed, long TableHandlesCreated, long TableHandlesDisposed); /// Explicit lifetime for weak collections on runtimes without finalization. /// The registry holds storage, never a strong table reference. A table's existing /// key/value semantics therefore cannot be strengthened by registry membership. public sealed class WeakCollectionScope { private sealed class Registration(object owner, IWeakStorage storage) { internal GCHandle Owner = GCHandle.Alloc(owner, GCHandleType.Weak); internal readonly IWeakStorage Storage = storage; } private readonly List registrations = []; private long created, disposed, tableCreated, tableDisposed; private bool released; internal void EnsureActive() => ObjectDisposedException.ThrowIf(released, this); internal void Register(object owner, IWeakStorage storage) { EnsureActive(); registrations.Add(new(owner, storage)); ++tableCreated; } internal void Created() => ++created; internal void Disposed() => ++disposed; public WeakCollectionLifetime Prune() { int entries = 0; for (int index = registrations.Count - 1; index >= 0; --index) { var item = registrations[index]; if (item.Owner.Target is null) { item.Storage.Clear(); item.Owner.Free(); ++tableDisposed; registrations.RemoveAt(index); } else entries += item.Storage.Prune(); } return new(registrations.Count, entries, created, disposed, tableCreated, tableDisposed); } public void Release() { if (released) return; foreach (var item in registrations) { item.Storage.Clear(); item.Owner.Free(); ++tableDisposed; } registrations.Clear(); released = true; } public WeakCollectionLifetime Lifetime => new(registrations.Count, registrations.Sum(item => item.Storage.Count), created, disposed, tableCreated, tableDisposed); } internal interface IWeakStorage { int Count { get; } int Prune(); void Clear(); } /// Storage-only replacement for Jint's original ConditionalWeakTable. /// Jint retains its intrinsic constructors, prototypes, validation and algorithms. public sealed class OwnedWeakTable where TKey : class where TValue : class { private sealed class Storage(WeakCollectionScope scope) : IWeakStorage { internal sealed class Entry(TKey key, TValue value) { internal DependentHandle Handle = new(key, value); } internal readonly Dictionary> Buckets = []; private readonly List empty = []; public int Count { get; private set; } internal void Added() { ++Count; scope.Created(); } internal void Free(Entry entry) { entry.Handle.Dispose(); --Count; scope.Disposed(); } public int Prune() { foreach (var item in Buckets) { var bucket = item.Value; for (int index = bucket.Count - 1; index >= 0; --index) if (bucket[index].Handle.Target is null) { Free(bucket[index]); bucket.RemoveAt(index); } if (bucket.Count == 0) empty.Add(item.Key); } foreach (int hash in empty) Buckets.Remove(hash); empty.Clear(); return Count; } public void Clear() { foreach (var bucket in Buckets.Values) foreach (var entry in bucket) Free(entry); Buckets.Clear(); empty.Clear(); } } private readonly Storage storage; private readonly WeakCollectionScope scope; public OwnedWeakTable(WeakCollectionScope scope) { ArgumentNullException.ThrowIfNull(scope); this.scope=scope; storage = new(scope); scope.Register(this, storage); } public bool TryGetValue(TKey key, out TValue value) { scope.EnsureActive(); ArgumentNullException.ThrowIfNull(key); if (storage.Buckets.TryGetValue(RuntimeHelpers.GetHashCode(key), out var bucket)) foreach (var entry in bucket) { var pair = entry.Handle.TargetAndDependent; if (ReferenceEquals(pair.Target, key)) { value = (TValue)pair.Dependent!; return true; } } value = null!; return false; } public void Add(TKey key, TValue value) => Store(key, value, false); public void AddOrUpdate(TKey key, TValue value) => Store(key, value, true); // Keep the original callback type so the interpreter's cached delegates and // its lazy shape factory remain unchanged. A factory may re-enter this table. public TValue GetValue(TKey key, ConditionalWeakTable.CreateValueCallback createValueCallback) { scope.EnsureActive(); ArgumentNullException.ThrowIfNull(createValueCallback); if (TryGetValue(key, out var present)) return present; var created = createValueCallback(key); if (TryGetValue(key, out present)) return present; Add(key, created); return created; } // The pinned private-element store supplies its concrete dictionary factory; // this avoids reflection-based construction on NativeAOT. public TValue GetOrCreateValue(TKey key, Func factory) { scope.EnsureActive(); ArgumentNullException.ThrowIfNull(factory); if (TryGetValue(key, out var present)) return present; var created = factory(); if (TryGetValue(key, out present)) return present; Add(key, created); return created; } private void Store(TKey key, TValue value, bool update) { scope.EnsureActive(); ArgumentNullException.ThrowIfNull(key); int hash = RuntimeHelpers.GetHashCode(key); if (!storage.Buckets.TryGetValue(hash, out var bucket)) storage.Buckets.Add(hash, bucket = []); for (int index = bucket.Count - 1; index >= 0; --index) { var entry = bucket[index]; var pair = entry.Handle.TargetAndDependent; if (pair.Target is null) { storage.Free(entry); bucket.RemoveAt(index); continue; } if (!ReferenceEquals(pair.Target, key)) continue; if (!update) throw new ArgumentException("The weak identity key already exists.", nameof(key)); entry.Handle.Dependent = value; return; } bucket.Add(new(key, value)); storage.Added(); } public bool Remove(TKey key) { scope.EnsureActive(); ArgumentNullException.ThrowIfNull(key); int hash = RuntimeHelpers.GetHashCode(key); if (storage.Buckets.TryGetValue(hash, out var bucket)) for (int index = 0; index < bucket.Count; ++index) { var entry = bucket[index]; if (!ReferenceEquals(entry.Handle.Target, key)) continue; storage.Free(entry); bucket.RemoveAt(index); if (bucket.Count == 0) storage.Buckets.Remove(hash); return true; } return false; } }