The procedural generator registry for the mapping tools. Defines the abstract GeneratorSettings base (seeds, variation, JSON round-trip copy), the IPartGenerator interface that turns settings into a PartPlan, and a static GeneratorRegistry that discovers generator types through EditorTypeLibrary and saves, loads and merges their settings as System.Text.Json.
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AdvancedMappingTools;
/// <summary>
/// Settings of a generator. They are stored on the root part in the scene.
/// </summary>
public abstract class GeneratorSettings
{
public SeedSet Seeds { get; set; } = new();
[Property, Group( "Variation" ), Range( 0, 1 ), WideMode, Description( "How much parts differ from each other." )]
public float Variation { get; set; } = 0.3f;
/// <summary>
/// Common options these settings have no use for. They stay out of the inspector.
/// </summary>
[JsonIgnore] public virtual IReadOnlySet<string> UnusedOptions => null;
/// <summary>
/// Take over all values from other settings of the same kind. For presets, which only
/// have values to give, while a tool keeps showing the object it has.
/// </summary>
public void CopyFrom( GeneratorSettings other )
{
if ( other is null || other.GetType() != GetType() )
throw new ArgumentException( "Settings must have the same generator type.", nameof( other ) );
var copy = (GeneratorSettings)JsonSerializer.Deserialize( JsonSerializer.Serialize( other, other.GetType() ), other.GetType() );
Seeds = copy.Seeds ?? new SeedSet();
foreach ( var property in EditorTypeLibrary.GetType( GetType() ).Properties.Where( x => x.HasAttribute<PropertyAttribute>() && x.CanWrite ) )
property.SetValue( this, property.GetValue( copy ) );
}
}
/// <summary>
/// Makes a plan from settings. A generator touches no scene and no interface, which
/// is what lets one be tested alone and called by another.
/// </summary>
public interface IPartGenerator
{
string Id { get; }
string Title { get; }
Type SettingsType { get; }
/// <summary>
/// The seed categories this generator reads.
/// </summary>
IReadOnlyList<string> SeedNames => SeedSet.Names;
PartPlan Generate( GeneratorSettings settings, GenContext context );
}
/// <summary>
/// Finds generators by id. A new generator only has to exist to be found.
/// </summary>
public static class GeneratorRegistry
{
public static IEnumerable<IPartGenerator> All =>
EditorTypeLibrary.GetTypes<IPartGenerator>()
.Where( x => !x.IsAbstract && !x.IsInterface )
.Select( x => x.Create<IPartGenerator>() );
public static IPartGenerator Find( string id ) => All.FirstOrDefault( x => x.Id == id );
public static string Save( GeneratorSettings settings ) =>
JsonSerializer.Serialize( settings, settings.GetType() );
/// <summary>
/// Options and seeds of other settings on top of what an object already is. Its line, its
/// size and its connections are no options, they stay.
/// </summary>
public static GeneratorSettings Merge( IPartGenerator generator, GeneratorSettings current, GeneratorSettings values )
{
var merged = Load( generator, Save( current ) );
merged.CopyFrom( values );
return merged;
}
public static GeneratorSettings Load( IPartGenerator generator, string json ) =>
(GeneratorSettings)JsonSerializer.Deserialize( json, generator.SettingsType );
}