Editor/Data/ArchCatalogs.cs
namespace Sunless.Architecture;
// Which shelf a catalog stands on. A role rather than an interface per catalog, because the catalogs differ only in
// what they hold - a module brings its own with new ArchShelf( role ), and the reservation only stops two of them
// meaning the same thing.
public readonly record struct ArchShelf( string Role ) {
public static readonly ArchShelf Openings = new( "openings" );
public static readonly ArchShelf Walls = new( "walls" );
public static readonly ArchShelf Pillars = new( "pillars" );
public static readonly ArchShelf RoadLines = new( "roadlines" );
public static readonly ArchShelf Profiles = new( "profiles" );
public static readonly ArchShelf Cornices = new( "cornices" );
public static readonly ArchShelf Cabinets = new( "cabinets" );
public static readonly ArchShelf Types = new( "types" );
public override string ToString() => Role;
}
// One catalog of authored types, owned by the module whose subtool authors them. The TYPE itself stays in core: a
// porch fits an opening preset it never authored, so a preset is shared geometry by the same rule a payload is,
// while the catalog around it belongs to one module alone.
public interface IArchCatalog {
ArchShelf Shelf { get; }
string Named( object entry );
// What the module brings with it; an authored entry of the same name wins, so a tuned preset survives upgrades.
IEnumerable<object> Shipped();
// What stands on disk beside it, which tops what ships.
IEnumerable<object> Authored();
bool Save( object entry );
}
public sealed class ArchCatalogs : ArchTable<ArchCatalogs, ArchShelf, IArchCatalog> {
protected override IEnumerable<IArchCatalog> Standing() {
yield return new ArchArchetypeCatalog();
yield return new ArchProfileCatalog();
yield return new ArchCorniceCatalog();
yield return new ArchCabinetCatalog();
yield return new ArchOpeningCatalog();
yield return new ArchWallCatalog();
yield return new ArchPillarCatalog();
yield return new ArchRoadLineCatalog();
}
protected override ArchShelf KeyOf( IArchCatalog catalog ) => catalog.Shelf;
protected override bool Accepts( IArchCatalog catalog ) => !string.IsNullOrWhiteSpace( catalog.Shelf.Role );
protected override string Collision( IArchCatalog standing, IArchCatalog catalog ) {
return $"{catalog.Shelf} is claimed by both {standing.GetType().Name} and {catalog.GetType().Name}";
}
// A shelf no catalog claims stands empty rather than erroring.
public IArchCatalog On( ArchShelf shelf ) => Held( shelf );
public List<T> Ships<T>( ArchShelf shelf ) where T : class {
return On( shelf ) is { } catalog ? catalog.Shipped().OfType<T>().ToList() : new List<T>();
}
// For a catalog whose authored folder IS the offering - a picker that browses what an author has on disk.
public List<T> Authors<T>( ArchShelf shelf ) where T : class {
return On( shelf ) is { } catalog ? catalog.Authored().OfType<T>().ToList() : new List<T>();
}
// What ships never displaces what is already held, and what is authored on disk always does - so an upgrade
// brings new presets in beside the tuned ones and a written file is the last word on its own name.
public void Stock<T>( ArchShelf shelf, List<T> saved ) where T : class {
if ( saved is null || On( shelf ) is not { } catalog ) {
return;
}
foreach ( var entry in catalog.Shipped().OfType<T>() ) {
if ( !saved.Any( existing => Same( catalog.Named( existing ), catalog.Named( entry ) ) ) ) {
saved.Add( entry );
}
}
foreach ( var entry in catalog.Authored().OfType<T>() ) {
var index = saved.FindIndex( existing => Same( catalog.Named( existing ), catalog.Named( entry ) ) );
if ( index >= 0 ) {
saved[index] = entry;
} else {
saved.Add( entry );
}
}
}
public bool Save( ArchShelf shelf, object entry ) => On( shelf ) is { } catalog && catalog.Save( entry );
static bool Same( string first, string second ) => string.Equals( first, second, StringComparison.OrdinalIgnoreCase );
}
// For a one-off caller holding no table of its own. Reaching through it in a loop loads the shelf every time.
public static class ArchShelved {
public static List<T> Ships<T>( ArchShelf shelf ) where T : class => ArchCatalogs.Load().Ships<T>( shelf );
public static List<T> Authors<T>( ArchShelf shelf ) where T : class => ArchCatalogs.Load().Authors<T>( shelf );
public static bool Save( ArchShelf shelf, object entry ) => ArchCatalogs.Load().Save( shelf, entry );
}