Editor/Services/ArchBuildMemo.cs
using System;
using System.Collections.Generic;

namespace Sunless.Architecture;

// Per-build cache opened AFTER connections resolve; a generator must not mutate the plan.
public sealed class ArchBuildMemo : IDisposable {
	public static ArchBuildMemo Current { get; private set; }

	readonly ArchBuildMemo held;
	readonly ArchPlan plan;

	ArchBuildMemo( ArchPlan plan ) {
		this.plan = plan;

		held = Current;
		Current = this;
	}

	public static IDisposable Begin( ArchPlan plan ) => new ArchBuildMemo( plan );

	public void Dispose() => Current = held;

	public Dictionary<int, float> Lifts { get; } = new();

	public Dictionary<int, List<ArchCarveVolume>> Volumes { get; } = new();

	public Dictionary<(int Affector, int Host), IReadOnlySet<int>> Reaches { get; } = new();

	public Dictionary<int, ArchSiteAssembly> Holders { get; } = new();

	Dictionary<int, ArchLayerRecord> records;

	// Every Applies call used to linear-scan the whole record list, once per operation per host.
	public Dictionary<int, ArchLayerRecord> Records {
		get {
			if ( records is not null ) {
				return records;
			}

			records = new Dictionary<int, ArchLayerRecord>();

			foreach ( var record in plan?.Layers ?? new List<ArchLayerRecord>() ) {
				records[record.ItemId] = record;
			}

			return records;
		}
	}

	// Held if a build is open, resolved plainly if not - so no caller needs to know whether one is.
	public static TValue Held<TKey, TValue>( Func<ArchBuildMemo, Dictionary<TKey, TValue>> table, TKey key, Func<TValue> resolve ) {
		if ( Current is not { } memo ) {
			return resolve();
		}

		var into = table( memo );

		if ( into.TryGetValue( key, out var found ) ) {
			return found;
		}

		return into[key] = resolve();
	}
}