Editor/TypeLibraryFixes/TypeSerializedObjectFixed.cs
using Sandbox;
using Sandbox.Diagnostics;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace ExtendedEditor.TypeLibraryFixes;
public class TypeSerializedObjectFixed : SerializedObject
{
private static readonly MethodInfo _notePreChangeMethod = typeof(SerializedProperty)
.GetMethod("NotePreChange", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, [typeof(SerializedProperty)])!;
private static readonly Logger _logger = new("ExtendedEditor.TypeSerializedObject");
public override string TypeIcon => _displayInfo.Icon;
public override string TypeName => Type.Name;
public override string TypeTitle => _displayInfo.Name;
public override bool IsValid
{
get => _targetObject switch
{
IValid target => target.IsValid,
null => false,
_ => base.IsValid
};
}
private DisplayInfo _displayInfo;
private readonly Func<object?>? _fetchTarget;
public readonly Type Type;
internal object _targetObject;
private string? _fullName;
public TypeSerializedObjectFixed(object target, Type type, SerializedProperty? parent = null)
{
ArgumentNullException.ThrowIfNull(target, nameof(target));
ArgumentNullException.ThrowIfNull(type, nameof(type));
if(!target.GetType().IsAssignableTo(type))
throw new InvalidOperationException($"Got wrong target type target: {target}, type: {type}");
_targetObject = target;
Type = type;
ParentProperty = parent;
_displayInfo = DisplayInfo.ForMember(type, inherit: true);
BuildProperties();
}
public TypeSerializedObjectFixed(Func<object> fetchTarget, Type type, SerializedProperty? parent = null)
{
_fetchTarget = fetchTarget;
var target = _fetchTarget();
// only have to dynamically fetch value types
if(!type.IsValueType)
_fetchTarget = null;
ArgumentNullException.ThrowIfNull(target, nameof(target));
ArgumentNullException.ThrowIfNull(type, nameof(type));
if(!target.GetType().IsAssignableTo(type))
throw new InvalidOperationException($"Got wrong target type target: {target}, type: {type}");
_targetObject = target;
Type = type;
ParentProperty = parent;
_displayInfo = DisplayInfo.ForMember(type, inherit: true);
BuildProperties();
}
/// <summary>
/// Get the target object. If the target object is a value type, we'll
/// call FetchTarget() - which should fetch the latest copy of it from
/// the parent.
/// Note that by design FetchTarget is null if it's not a value type.
/// </summary>
internal ref object GetTargetObject()
{
if(_fetchTarget is not null)
{
Assert.True(Type.IsValueType);
_targetObject = _fetchTarget()!;
}
return ref _targetObject;
}
private void BuildProperties()
{
_fullName = Type.AssemblyQualifiedName;
PropertyList ??= [];
PropertyList.Clear();
foreach(var property in TypeCache.GetInstanceProperties(Type))
{
var serialized = new TypeSerializedPropertyFixed(this, property);
PropertyList.Add(serialized);
}
foreach(var field in TypeCache.GetInstanceFields(Type))
{
var serialized = new TypeSerializedPropertyFixed(this, field);
PropertyList.Add(serialized);
}
foreach(var method in TypeCache.GetInstanceMethods(Type))
{
var serialized = new TypeSerializedMethodFixed(this, method);
PropertyList.Add(serialized);
}
}
public override void NoteChanged(SerializedProperty childProperty)
{
ParentProperty?.SetValue(_targetObject, childProperty);
if(childProperty is not null)
base.NoteChanged(childProperty);
}
public override void NotePreChange(SerializedProperty childProperty)
{
if(ParentProperty is not null)
_notePreChangeMethod.Invoke(ParentProperty, [childProperty]);
if(childProperty is not null)
{
base.NotePreChange(childProperty);
}
}
protected override void PrepareEnumerator()
{
if(_fullName != Type.AssemblyQualifiedName)
{
_logger.Trace($"Rebuilding - detected assembly change [{_fullName}] to [{Type.AssemblyQualifiedName}]");
BuildProperties();
}
}
public override IEnumerable<object> Targets
{
get
{
yield return GetTargetObject();
}
}
}