Editor/Modules/ArchTable.cs
namespace Sunless.Architecture;
// The scaffold every table shares: loaded per ask because hotload carries statics over, refusing a second claim on
// a key rather than letting the order they were written in pick the winner, and standing a copy with one key gone
// so a case can probe the shape a caller sees when nothing answers.
//
// Standing is written out by each table rather than scanned for. One assembly declares every entry, so a scan would
// only be a slower way of naming what the table already knows, and each type handle it walked outlives the edit.
public abstract class ArchTable<TSelf, TKey, TEntry>
where TSelf : ArchTable<TSelf, TKey, TEntry>, new()
where TEntry : class {
readonly Dictionary<TKey, TEntry> held = new();
readonly List<string> collisions = new();
protected abstract TKey KeyOf( TEntry entry );
protected abstract string Collision( TEntry standing, TEntry entry );
protected abstract IEnumerable<TEntry> Standing();
protected virtual bool Accepts( TEntry entry ) => true;
public static TSelf Load() {
var built = new TSelf();
foreach ( var entry in built.Standing() ) {
built.Enrol( entry );
}
// The game's own entries after the tool's, so a written-out row always wins a key an addon also claims.
foreach ( var contributed in ArchAddons.Load().Contributed<TEntry>() ) {
built.Enrol( contributed );
}
return built;
}
public TSelf Enrol( TEntry entry ) {
if ( entry is null || !Accepts( entry ) ) {
return (TSelf)this;
}
var key = KeyOf( entry );
if ( held.TryGetValue( key, out var standing ) ) {
collisions.Add( Collision( standing, entry ) );
return (TSelf)this;
}
held[key] = entry;
return (TSelf)this;
}
public TSelf Without( TKey key ) {
var built = new TSelf();
foreach ( var entry in held.Values ) {
if ( !EqualityComparer<TKey>.Default.Equals( KeyOf( entry ), key ) ) {
built.Enrol( entry );
}
}
return built;
}
public IReadOnlyList<string> Collisions => collisions;
protected TEntry Held( TKey key ) {
return key is not null && held.TryGetValue( key, out var entry ) ? entry : null;
}
protected IReadOnlyCollection<TEntry> Entries => held.Values;
}