Editor/Modules/ArchDiscovery.cs
namespace Sunless.Architecture;
// The ONE place the tool scans for a type, and it is only ever for a HOST HOOK - IArchHost and IArchDoorFitter,
// both answered by the game around the tool, which a library cannot name. Every other table writes out what stands
// in it: one assembly declares them all, and a scan holds a type handle for every hotloaded assembly the editor
// will never unload.
//
// A type that cannot be stood up used to take its whole table with it: TypeDescription.Create falls through to
// Activator.CreateInstance, which THROWS for a type with no parameterless constructor rather than handing back
// null. So nothing opens a type itself; what cannot stand is walked past and the games that can still answer.
public static class ArchDiscovery {
public static IEnumerable<T> Enrolled<T>() where T : class {
return EditorTypeLibrary.GetTypes<T>()
.Where( Constructible )
.Select( Made<T> )
.Where( found => found is not null );
}
// For a table whose answer must not depend on which assembly the editor loaded first.
public static IEnumerable<T> EnrolledByName<T>() where T : class {
return EditorTypeLibrary.GetTypes<T>()
.Where( Constructible )
.OrderBy( type => type.ClassName )
.Select( Made<T> )
.Where( found => found is not null );
}
static bool Constructible( TypeDescription type ) {
if ( type.IsAbstract || type.IsInterface || type.IsGenericType ) {
return false;
}
return type.TargetType.IsValueType || type.TargetType.GetConstructor( Type.EmptyTypes ) is not null;
}
static T Made<T>( TypeDescription type ) where T : class {
try {
return type.Create<T>();
} catch ( Exception fault ) {
Log.Warning( $"{type.ClassName} declares {typeof( T ).Name} but threw while being constructed: {fault.Message}" );
return null;
}
}
}