Editor/Services/ArchBuildMemo.cs

A short-lived ambient cache for architectural build data. It installs itself as a static Current memo while a build is open and stores precomputed maps (lifts, carve volumes, reach sets, holders and layer records) to avoid recomputing answers repeatedly during a build.

File Access
using System;
using System.Collections.Generic;

namespace Sunless.Architecture;

// Answers that are the SAME for a whole build and were being re-derived per PART: a building's grade lift, a cut's
// resolved volumes, what a layer may reach, and a layer's own record. Ambient the way ArchLayerGate is, because the
// callers are static services far below anything that could carry a context.
//
// Opened only AFTER connections resolve - Normalize moves footprints, and a lift held across that is a lie. Nothing
// inside a build may mutate the plan; a generator that does invalidates every answer held here.
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();
	}
}