Editor/Modules/ArchDiscovery.cs

Editor utility that discovers and instantiates editor-available implementations of generic interfaces or classes. It queries an EditorTypeLibrary for TypeDescription entries, filters to constructible types, optionally orders by class name, and constructs instances while catching construction exceptions.

Reflection
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;
		}
	}
}