Editor/Output/ArchBuildGate.cs
using System.Collections.Generic;
namespace Sunless.Architecture;
// Which scopes this build may leave alone entirely - not generated, not finished, not touched. A scope is a
// building or a road, keyed through ArchLayerHash: the group it stands in, plus everything that reaches out of
// every group. Nothing here decides what a layer's identity IS; it only asks.
//
// Absent, every scope builds - which is what a cold build, a designer stage and a preview all want.
public sealed class ArchBuildGate {
readonly Dictionary<int, ulong> standing;
readonly IReadOnlySet<int> refused;
readonly IReadOnlySet<int> rebuild;
readonly ArchPlan plan;
readonly ulong wide;
public ArchBuildGate( ArchPlan plan, ArchKit kit, int terrain, Dictionary<int, ulong> standing, IReadOnlySet<int> refused,
IReadOnlySet<int> rebuild = null ) {
this.plan = plan;
this.standing = standing;
this.refused = refused;
this.rebuild = rebuild;
wide = ArchLayerHash.Wide( plan, kit, terrain );
}
public Dictionary<int, ulong> Keys { get; } = new();
// The group roll-ups worked out so far. Every member of a group asks for the same one, and working it out is a
// serialise of the whole group - so a plan is only asked once per group per build rather than once per member.
readonly Dictionary<int, ulong> rolled = new();
// Asked once per scope, and it RECORDS as it answers - the key it just computed is what the next build
// compares against, so a scope can never be skipped twice off one stale reading.
public bool Skips( int itemId ) {
var key = ArchLayerHash.Scope( wide, plan, itemId, rolled );
Keys[itemId] = key;
if ( refused is not null && refused.Contains( itemId ) ) {
return false;
}
if ( rebuild is not null && !rebuild.Contains( itemId ) ) {
return true;
}
return standing is not null && standing.TryGetValue( itemId, out var held ) && held == key;
}
}