Editor/Data/ArchPlanStore.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
namespace Sunless.Architecture;
public static class ArchPlanStore {
public static IEnumerable<object> Everything( this ArchPlan plan, ArchKinds table = null ) {
if ( plan is null ) {
yield break;
}
var kinds = table ?? ArchKinds.Load();
var seen = new HashSet<object>( ReferenceEqualityComparer.Instance );
var pending = new Queue<object>();
foreach ( var root in plan.Units.Cast<object>().Concat( plan.Assemblies ).Concat( plan.Instances ) ) {
pending.Enqueue( root );
}
while ( pending.Count > 0 ) {
var host = pending.Dequeue();
if ( host is null || !seen.Add( host ) ) {
continue;
}
yield return host;
foreach ( var filed in kinds.All.Where( manifest => Hosts( manifest, host ) ).SelectMany( manifest => manifest.Filed( plan, host ) ) ) {
pending.Enqueue( filed );
}
}
}
public static IEnumerable<T> Parts<T>( this ArchPlan plan, ArchKinds table = null ) where T : class {
return plan.Everything( table ).OfType<T>();
}
public static IEnumerable<object> Parts( this ArchPlan plan, ArchKind kind, ArchKinds table = null ) {
if ( plan is null ) {
yield break;
}
var kinds = table ?? ArchKinds.Load();
var manifest = kinds.For( kind );
if ( manifest is null ) {
yield break;
}
var seen = new HashSet<object>( ReferenceEqualityComparer.Instance );
foreach ( var part in manifest.Filed( plan, null ).Where( seen.Add ) ) {
yield return part;
}
foreach ( var host in plan.Everything( kinds ).Where( candidate => Hosts( manifest, candidate ) ) ) {
foreach ( var part in manifest.Filed( plan, host ).Where( seen.Add ) ) {
yield return part;
}
}
}
public static T Find<T>( this ArchPlan plan, int id, ArchKinds table = null ) where T : class {
return id == 0 ? null : plan.Parts<T>( table ).FirstOrDefault( part => IdOf( part ) == id );
}
public static object Find( this ArchPlan plan, ArchKind kind, int id, ArchKinds table = null ) {
return id == 0 ? null : plan.Parts( kind, table ).FirstOrDefault( part => IdOf( part ) == id );
}
public static object Find( this ArchPlan plan, int id, ArchKinds table = null ) {
return id == 0 ? null : plan.Everything( table ).FirstOrDefault( part => IdOf( part ) == id );
}
public static THost HostOf<THost>( this ArchPlan plan, object part, ArchKinds table = null ) where THost : class {
if ( part is null ) {
return null;
}
var kinds = table ?? ArchKinds.Load();
return plan.Everything( kinds ).OfType<THost>().FirstOrDefault( host => Files( plan, kinds, host, part ) );
}
public static object HostOf( this ArchPlan plan, object part, ArchKinds table = null ) {
if ( part is null ) {
return null;
}
var kinds = table ?? ArchKinds.Load();
return plan.Everything( kinds ).FirstOrDefault( host => Files( plan, kinds, host, part ) );
}
public static IEnumerable<T> Units<T>( this ArchPlan plan ) where T : ArchUnit => plan.Units.OfType<T>();
public static IEnumerable<ArchUnit> Units( this ArchPlan plan, ArchKind kind ) => plan.Units.Where( unit => unit.Kind == kind );
public static IEnumerable<object> Filed( this ArchPlan plan, ArchKind kind, object host, ArchKinds table = null ) {
if ( host is null && plan is null ) {
return Enumerable.Empty<object>();
}
var manifest = (table ?? ArchKinds.Load()).For( kind );
return manifest is null ? Enumerable.Empty<object>() : manifest.Filed( plan, host );
}
public static IEnumerable<object> FiledOn( ArchKind kind, object host, ArchKinds table = null ) {
return host is null ? Enumerable.Empty<object>() : Filed( null, kind, host, table );
}
public static IEnumerable<T> Filed<T>( this ArchPlan plan, object host, ArchKinds table = null ) where T : class {
var kinds = table ?? ArchKinds.Load();
return kinds.Claiming( typeof( T ) ) is { } manifest
? manifest.Filed( plan, host ).OfType<T>()
: kinds.All.Where( entry => Hosts( entry, host ) ).SelectMany( entry => entry.Filed( plan, host ) ).OfType<T>();
}
public static int CountFiled( this ArchPlan plan, ArchKind kind, object host, ArchKinds table = null ) {
return plan.Filed( kind, host, table ).Count();
}
public static int CountFiled<T>( this ArchPlan plan, object host, ArchKinds table = null ) where T : class {
return plan.Filed<T>( host, table ).Count();
}
public static bool AnyFiled( this ArchPlan plan, object host, ArchKinds table = null ) {
if ( plan is null ) {
return false;
}
var kinds = table ?? ArchKinds.Load();
return kinds.All.Where( manifest => Hosts( manifest, host ) ).Any( manifest => manifest.Filed( plan, host ).Any() );
}
public static bool File( this ArchPlan plan, ArchKind kind, object host, object payload, ArchKinds table = null ) {
return (table ?? ArchKinds.Load()).For( kind )?.File( host, payload ) == true;
}
// Index is within the kind's filed order, not the raw list
public static bool Insert( this ArchPlan plan, ArchKind kind, object host, int index, object payload, ArchKinds table = null ) {
var manifest = plan is null || payload is null ? null : (table ?? ArchKinds.Load()).For( kind );
var folder = manifest?.Folder( plan, host );
if ( folder is null ) {
return false;
}
var seat = SeatFor( folder, manifest.Filed( plan, host ).ToList(), index );
if ( !manifest.File( host, payload ) ) {
return false;
}
var landed = SeatOf( folder, payload );
if ( landed >= 0 && landed != seat ) {
folder.RemoveAt( landed );
folder.Insert( seat, payload );
}
return true;
}
public static bool RemoveAt( this ArchPlan plan, ArchKind kind, object host, int index, ArchKinds table = null ) {
var manifest = plan is null || index < 0 ? null : (table ?? ArchKinds.Load()).For( kind );
var filed = manifest?.Filed( plan, host ).ToList();
return filed is not null && index < filed.Count && manifest.Unfile( plan, filed[index] );
}
public static int RemoveAll( this ArchPlan plan, ArchKind kind, object host, Func<object, bool> predicate, ArchKinds table = null ) {
var manifest = plan is null || predicate is null ? null : (table ?? ArchKinds.Load()).For( kind );
if ( manifest is null ) {
return 0;
}
return manifest.Filed( plan, host ).Where( predicate ).ToList().Count( doomed => manifest.Unfile( plan, doomed ) );
}
public static bool Unfile( this ArchPlan plan, object payload, ArchKinds table = null ) {
if ( plan is null || payload is null ) {
return false;
}
var kinds = table ?? ArchKinds.Load();
return kinds.Claiming( payload )?.Unfile( plan, payload ) == true
|| kinds.All.Any( manifest => manifest.Unfile( plan, payload ) );
}
// Reflected — never cached across hotloads
public static int IdOf( object payload ) {
return payload?.GetType().GetProperty( "Id" )?.GetValue( payload ) is int id ? id : 0;
}
public static bool SetId( object payload, int id ) {
if ( payload?.GetType().GetProperty( "Id" ) is not { CanWrite: true } property || property.PropertyType != typeof( int ) ) {
return false;
}
property.SetValue( payload, id );
return true;
}
// Watermark from opaque payloads — prevents id reuse across unknown kinds
public static int HighestIdIn( IReadOnlyDictionary<string, JsonElement> payloads ) {
return payloads is null ? 0 : payloads.Values.Select( HighestIdIn ).DefaultIfEmpty( 0 ).Max();
}
public static int HighestIdIn( JsonElement element ) {
switch ( element.ValueKind ) {
case JsonValueKind.Object:
var highest = 0;
foreach ( var property in element.EnumerateObject() ) {
var found = property.NameEquals( "Id" ) && property.Value.ValueKind == JsonValueKind.Number
&& property.Value.TryGetInt32( out var id )
? id
: HighestIdIn( property.Value );
highest = System.Math.Max( highest, found );
}
return highest;
case JsonValueKind.Array:
return element.EnumerateArray().Select( HighestIdIn ).DefaultIfEmpty( 0 ).Max();
default:
return 0;
}
}
static bool Hosts( IArchKindManifest manifest, object host ) {
return manifest.Hosts.Any( type => type.IsInstanceOfType( host ) );
}
static bool Files( ArchPlan plan, ArchKinds kinds, object host, object part ) {
return kinds.All.Where( manifest => Hosts( manifest, host ) )
.SelectMany( manifest => manifest.Filed( plan, host ) )
.Any( filed => ReferenceEquals( filed, part ) );
}
static int SeatFor( System.Collections.IList folder, IReadOnlyList<object> filed, int index ) {
var seat = index >= 0 && index < filed.Count ? SeatOf( folder, filed[index] ) : -1;
return seat < 0 ? folder.Count : seat;
}
static int SeatOf( System.Collections.IList folder, object payload ) {
for ( var seat = 0; seat < folder.Count; seat++ ) {
if ( ReferenceEquals( folder[seat], payload ) ) {
return seat;
}
}
return -1;
}
}