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.
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();
}
}