Editor/TypeResolver.cs
using Editor;
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace ExtendedEditor;
public static class TypeResolver
{
private static readonly Dictionary<string, HashSet<Type>> _sandboxCategories = new()
{
{"System", [
typeof(Color),
typeof(Color32),
typeof(Vector2),
typeof(Vector2Int),
typeof(Vector3),
typeof(Vector3Int),
typeof(Vector4),
typeof(Angles),
typeof(Rotation),
typeof(Rect),
typeof(RectInt),
typeof(Sphere),
typeof(Capsule),
typeof(GameTransform),
typeof(TagSet),
]},
{"Network", [
typeof(OwnerTransfer),
#pragma warning disable CS0612 // Type or member is obsolete
typeof(NetPermission),
#pragma warning restore CS0612 // Type or member is obsolete
]},
{"Time", [
typeof(TimeSince),
typeof(TimeUntil),
typeof(RealTimeSince),
typeof(RealTimeUntil),
]},
};
private static readonly Dictionary<string, List<string>> _sandboxNamedCategories = new()
{
{ "Physics" , ["Physics", "Collision", "Collider", "Hitbox", "Rigidbody"]},
{ "Scene" , ["Scene"]},
{ "Particle" , ["Particle"]},
{ "Object" , ["Object"]},
{ "Network" , ["Network", "Connection", "Rpc", "StaticRpc"]},
{ "Input" , ["Input", "Mouse", "Keyboard", "Gamepad"]},
{ "Aniamation" , ["Bone"]},
{ "Models" , ["Model", "Vertex", "Polygon", "Mesh"]},
{ "GameObject" , ["GameObject"]},
{ "Scenes" , ["PrefabScene", "Scene"]},
{ "Prefab" , ["Prefab"]},
{ "Audio" , ["Audio", "Sound", "Stereo"]},
{ "Video" , ["Video", "Image", "Render"]},
{ "Debug" , ["Debug"]},
};
private static readonly Dictionary<string, HashSet<Type>> _systemCategories = new()
{
{"Tuples", [
typeof(Tuple<>),
typeof(Tuple<,>),
typeof(Tuple<,,>),
typeof(Tuple<,,,>),
typeof(Tuple<,,,,>),
typeof(Tuple<,,,,,>),
typeof(Tuple<,,,,,,>),
typeof(Tuple<,,,,,,,>),
]},
{"Actions", [
typeof(Action),
typeof(Action<>),
typeof(Action<,>),
typeof(Action<,,>),
typeof(Action<,,,>),
typeof(Action<,,,,>),
typeof(Action<,,,,,>),
typeof(Action<,,,,,,>),
typeof(Action<,,,,,,,>),
typeof(Action<,,,,,,,,>),
typeof(Action<,,,,,,,,,>),
typeof(Action<,,,,,,,,,,>),
typeof(Action<,,,,,,,,,,,>),
typeof(Action<,,,,,,,,,,,,>),
typeof(Action<,,,,,,,,,,,,,>),
typeof(Action<,,,,,,,,,,,,,,>),
typeof(Action<,,,,,,,,,,,,,,,>),
]},
{"Functions", [
typeof(Func<>),
typeof(Func<,>),
typeof(Func<,,>),
typeof(Func<,,,>),
typeof(Func<,,,,>),
typeof(Func<,,,,,>),
typeof(Func<,,,,,,>),
typeof(Func<,,,,,,,>),
typeof(Func<,,,,,,,,>),
typeof(Func<,,,,,,,,,>),
typeof(Func<,,,,,,,,,,>),
typeof(Func<,,,,,,,,,,,>),
typeof(Func<,,,,,,,,,,,,>),
typeof(Func<,,,,,,,,,,,,,>),
typeof(Func<,,,,,,,,,,,,,,>),
typeof(Func<,,,,,,,,,,,,,,,>),
typeof(Func<,,,,,,,,,,,,,,,,>),
typeof(Predicate<>),
]},
{"Date", [
typeof(DateTime),
typeof(DateTimeKind),
typeof(DateTimeOffset),
typeof(DateOnly),
typeof(TimeOnly),
typeof(DayOfWeek),
]},
};
private static readonly HashSet<Type> _extraSandboxTypes = [
typeof(TemporaryEffect)
];
public static IReadOnlySet<Type> SystemTypes { get; } = typeof(string).Assembly.GetTypes().Where(t => t.Namespace?.StartsWith("System") ?? false).ToHashSet();
private static string FormatAssemblyName(Assembly asm)
{
var name = asm.GetName().Name!;
if(name.StartsWith("package.", StringComparison.OrdinalIgnoreCase))
name = name.Substring("package.".Length);
if(name.StartsWith("local.", StringComparison.OrdinalIgnoreCase))
name = name.Substring("local.".Length);
return name.ToTitleCase();
}
private static string GetAssemblyQualifiedPath(Type type)
{
var path = string.IsNullOrEmpty(type.Namespace)
? FormatAssemblyName(type.Assembly)
: type.Namespace.Replace('.', '/');
return path;
}
private static string GetTypePath(Type type)
{
if(type.DeclaringType is not null)
return $"{GetTypePath(type.DeclaringType)}/{type.Name}";
var typeDesc = TypeLibrary.GetType(type);
var prefix = typeDesc?.Group ?? GetAssemblyQualifiedPath(type);
if(prefix == "Sandbox System")
prefix = "Sandbox/System";
var icon = typeDesc?.Icon;
if(type.Namespace?.StartsWith("System") ?? false)
{
bool categoryFound = false;
if(type.IsValueType)
prefix += "/Value Types";
if(type.IsAssignableTo(typeof(ITuple)))
{
prefix += "/Tuples";
}
else if(type.IsAssignableTo(typeof(Exception)))
{
prefix += "/Exceptions";
}
else if(type.IsAssignableTo(typeof(Attribute)))
{
prefix += "/Attributes";
}
else
{
foreach(var (name, category) in _systemCategories)
{
if(category.Contains(type))
{
prefix += '/' + name;
categoryFound = true;
break;
}
}
}
if(!categoryFound)
{
if(type.IsInterface)
{
prefix += "/Interfaces";
}
}
}
else if((type.Namespace?.StartsWith("Sandbox") ?? false) || _extraSandboxTypes.Contains(type))
{
bool categoryFound = true;
if(type.IsAssignableTo(typeof(Resource)))
{
prefix = $"Resource/{prefix}";
icon ??= "description";
}
else if(type.IsAssignableTo(typeof(Component)))
{
prefix = $"Component/{prefix}";
icon ??= "category";
}
else
{
categoryFound = false;
}
if(!categoryFound)
{
categoryFound = true;
if(type.Name.EndsWith("Msg") || type.Name.EndsWith("MsgAck"))
prefix += "/Connection/Messages";
else
categoryFound = false;
}
if(!categoryFound)
{
foreach(var (name, category) in _sandboxCategories)
{
if(category.Contains(type))
{
prefix += '/' + name;
categoryFound = true;
break;
}
}
}
if(!categoryFound)
{
foreach(var (namedCategory, prefixes) in _sandboxNamedCategories)
{
if(prefixes.Any(type.Name.StartsWith))
{
prefix += '/' + namedCategory;
categoryFound = true;
break;
}
}
}
if(!prefix.StartsWith("Sandbox"))
prefix = "Sandbox/" + prefix;
}
icon ??= "check_box_outline_blank";
return $"{prefix}/{type.Name}:{icon}@2000";
}
public readonly record struct TypeOption
{
public Type Type { get; init; }
public Menu.PathElement[] Path { get; init; }
public string Title { get; init; }
public string? Description { get; init; }
public string? Icon { get; init; }
public TypeOption(Type type)
{
var typeDesc = TypeLibrary.GetType(type);
Path = Menu.GetSplitPath(GetTypePath(type));
Type = type;
Title = type.Name;
Description = typeDesc?.Description;
Icon = typeDesc?.Icon;
}
}
}