core/util/Spawn.cs
[AttributeUsage(AttributeTargets.Property)]
public class SpawnAttribute(string parent = null) : Attribute {
	public string Parent {get; set;} = parent;
}

public class Spawn {
	private static Component CurrentOwner {get; set;}
	public static void SetOwner(Component caller) {CurrentOwner = caller;}

	public static void GameObject(string property, string name, GameObject parent = null) {GameObject(CurrentOwner, property, name, parent);}
	public static void GameObject(Component owner, string property, string name, GameObject parent = null) {
		var propdesc = GetPropertyDescription(owner, property);
		if (propdesc == null) {
			Log.Error("Spawn.GameObject couldnt find property "+property);
			return;
		}
		ClearExisting(owner, propdesc);
		propdesc.SetValue(owner, SpawnObject(owner, name, parent));
	}
	public static void Component(string property, GameObject parent = null) {Component(CurrentOwner, property, null, parent);}
	public static void Component(Component caller, string property, Component owner = null, GameObject parent = null) {
		var propdesc = GetPropertyDescription(caller, property);
		if (propdesc == null) {
			Log.Error("Spawn.GameObject couldnt find property "+property);
			return;
		}
		ClearExisting(caller, propdesc);
		owner ??= caller;
		propdesc.SetValue(caller, SpawnComponent(TypeLibrary.GetType(propdesc.PropertyType), owner, caller, parent));
	}

	public static void Spawnables() {Spawnables(CurrentOwner, null);}
	public static void Spawnables(Component caller, Component owner = null) {
		var type = TypeLibrary.GetType(caller.GetType());
		foreach (var property in type.DeclaredMembers) {
			if (!property.IsProperty)
				continue;
			var attribute = property.GetCustomAttribute<SpawnAttribute>();
			if (attribute == null)
				continue;
			GameObject parent = null;
			if (!string.IsNullOrEmpty(attribute.Parent))
				parent = (GameObject)type.GetValue(caller, attribute.Parent);
			Component(caller, property.Name, owner, parent);
		}
	}

	private static PropertyDescription GetPropertyDescription(Component owner, string property) {
		foreach (var propdesc in TypeLibrary.GetPropertyDescriptions(owner)) {
			if (propdesc.Name == property)
				return propdesc;
		}
		return null;
	}

	private static void ClearExisting(Component owner, PropertyDescription propdesc) {
		var component = propdesc.GetValue(owner);
		if (component == null)
			return;
		if (component is GameObject obj)
			obj.Destroy();
		if (component is Component cmp)
			cmp.Destroy();
	}

	private static GameObject SpawnObject(Component owner, string name, GameObject parent) {
		var obj = owner.Scene.CreateObject();
		if (name[0] == '_')
			obj.Name = owner.GameObject.Name + name;
		else
			obj.Name = name;
		if (parent != null)
			obj.SetParent(parent, false);
		else
			obj.SetParent(owner.GameObject, false);
		return obj;
	}

	private static Component SpawnComponent(TypeDescription type, Component owner, Component caller, GameObject parent) {
		Component comp;
		if (parent != null)
			comp = parent.Components.Create(type);
		else
			comp = caller.Components.Create(type);
		GetPropertyDescription(comp, "Owner")?.SetValue(comp, owner);
		return comp;
	}
}