Editor helper types and registry for architecture drawing and filing. Defines ArchDrawing and ArchFiling context holders, role name lists, interfaces IArchDrawer/IArchFiler, and a loader/registry ArchDrawers that enrolls concrete drawer/filer implementations and exposes lookup and exclusion helpers.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// What one generator is handed when the builder asks it to draw: the canvas to fill, the layer payload it was
// asked about, the part being filed, and the ancestry it stands in. A generator reaches for what it needs by
// type, so the builder can hand a roof deck's connections to the wall generator without a signature saying so.
public sealed class ArchDrawing
{
public ArchMesh Canvas { get; init; }
public object Subject { get; init; }
public ArchBuiltPart Part { get; init; }
public ArchGenerationServices Services { get; init; }
public IReadOnlyList<object> Hosts { get; init; } = Array.Empty<object>();
public ArchPlan Plan => Services.Plan;
public ArchKit Kit => Services.Kit;
public ArchStyle Style => Services.Style;
public T Drawn<T>() => Subject is T subject ? subject : default;
public T Within<T>() => Hosts.OfType<T>().FirstOrDefault();
}
// What a generator that files its OWN parts is handed: the output service, the path to file under, and the same
// subject and ancestry a canvas drawer gets. A road comes out as an apron, a kerb and a line - many parts from
// one layer - so it opens them itself rather than filling a canvas the builder files.
public sealed class ArchFiling
{
public ArchOutputService Output { get; init; }
public string Path { get; init; }
public object Subject { get; init; }
public ArchGenerationServices Services { get; init; }
public IReadOnlyList<object> Hosts { get; init; } = Array.Empty<object>();
public ArchPlan Plan => Services.Plan;
public ArchKit Kit => Services.Kit;
public ArchStyle Style => Services.Style;
public T Drawn<T>() => Subject is T subject ? subject : default;
public T Within<T>() => Hosts.OfType<T>().FirstOrDefault();
}
static class ArchRoleNames
{
public static IReadOnlyList<string> Of( Type holder )
{
return holder.GetFields()
.Where( field => field.IsLiteral && field.FieldType == typeof( string ) )
.Select( field => (string)field.GetRawConstantValue() )
.ToList();
}
}
// The names the builder asks a canvas by. A role is not a payload type: a room is drawn as an overhang, as a
// rising shell and as boards, and a floor group is drawn as both a slab and a footing.
public static class ArchDraws
{
public const string Storey = "floor.storey";
public const string Foundation = "floor.foundation";
public const string Wall = "wall";
public const string WalkwayRising = "walkway.rising";
public const string WalkwayBoards = "walkway.boards";
public const string Roof = "roof";
public const string Downpipe = "downpipe";
public const string Pipe = "pipe";
public const string Bracket = "bracket";
public const string Ladder = "ladder";
public const string Balcony = "balcony";
public const string ExteriorStair = "stair.exterior";
public const string Fence = "fence";
public const string Cut = "cut";
public const string Platform = "platform";
public const string Overhang = "overhang";
public const string Pillar = "pillar";
public const string Span = "span";
public const string Beam = "beam";
public const string Stair = "stair";
public const string Trim = "trim";
public const string Porch = "porch";
public const string Approach = "approach";
public static IReadOnlyList<string> All { get; } = ArchRoleNames.Of( typeof( ArchDraws ) );
}
// The names the builder asks a whole set of parts by.
public static class ArchFiles
{
public const string Road = "road";
public const string Junction = "junction";
public const string GutterChannel = "gutter.channel";
public static IReadOnlyList<string> All { get; } = ArchRoleNames.Of( typeof( ArchFiles ) );
}
// One kind of thing the builder can be asked to draw. The implementation lives beside the generator it calls, so
// reading what a subtool draws never means reading the builder.
public interface IArchDrawer
{
string Draws { get; }
void Draw( ArchDrawing drawing );
}
public interface IArchFiler
{
string Files { get; }
void File( ArchFiling filing );
}
// Never cached across builds: hotload carries statics over, and a stale table would draw a scene with the
// generator code the editor had before the edit.
public sealed class ArchDrawers
{
readonly Dictionary<string, IArchDrawer> drawers = new();
readonly Dictionary<string, IArchFiler> filers = new();
public static ArchDrawers Load()
{
var built = new ArchDrawers();
built.Enrol( new ArchWallDrawer() );
built.Enrol( new ArchTrimDrawer() );
built.Enrol( new ArchBalconyDrawer() );
built.Enrol( new ArchLadderDrawer() );
built.Enrol( new ArchStoreyDrawer() );
built.Enrol( new ArchFoundationDrawer() );
built.Enrol( new ArchRoofDrawer() );
built.Enrol( new ArchDownpipeDrawer() );
built.Enrol( new ArchStairDrawer() );
built.Enrol( new ArchApproachDrawer() );
built.Enrol( new ArchExteriorStairDrawer() );
built.Enrol( new ArchPillarDrawer() );
built.Enrol( new ArchSpanDrawer() );
built.Enrol( new ArchPlatformDrawer() );
built.Enrol( new ArchBeamDrawer() );
built.Enrol( new ArchCutDrawer() );
built.Enrol( new ArchFenceDrawer() );
built.Enrol( new ArchPipeDrawer() );
built.Enrol( new ArchBracketDrawer() );
built.Enrol( new ArchPorchDrawer() );
built.Enrol( new ArchOverhangDrawer() );
built.Enrol( new ArchWalkwayRisingDrawer() );
built.Enrol( new ArchWalkwayBoardsDrawer() );
built.Enrol( new ArchGutterChannelFiler() );
built.Enrol( new ArchRoadFiler() );
built.Enrol( new ArchJunctionFiler() );
return built;
}
public ArchDrawers Enrol( IArchDrawer drawer )
{
drawers[drawer.Draws] = drawer;
return this;
}
public ArchDrawers Enrol( IArchFiler filer )
{
filers[filer.Files] = filer;
return this;
}
// The same table with one role gone, which is the negative control a drawer's absence is proved against.
public ArchDrawers Except( string role )
{
var built = new ArchDrawers();
foreach ( var enrolled in drawers.Where( entry => entry.Key != role ) )
{
built.drawers[enrolled.Key] = enrolled.Value;
}
foreach ( var enrolled in filers.Where( entry => entry.Key != role ) )
{
built.filers[enrolled.Key] = enrolled.Value;
}
return built;
}
// A role nothing answers draws nothing rather than erroring, which is what a negative control stands on.
public IArchDrawer For( string role ) => drawers.TryGetValue( role, out var drawer ) ? drawer : null;
public IArchFiler Filing( string role ) => filers.TryGetValue( role, out var filer ) ? filer : null;
public IEnumerable<string> Roles => drawers.Keys.Concat( filers.Keys );
}