115 lines
4.9 KiB
C#
115 lines
4.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using HarmonyLib;
|
|
using JetBrains.Annotations;
|
|
|
|
namespace Torch.Server.ViewModels;
|
|
|
|
public class DynamicViewModel<T>(T obj) : ViewModel
|
|
{
|
|
private readonly T _obj = obj;
|
|
|
|
public ViewModel Wrapper { get; } = CreateProxy(obj);
|
|
|
|
public static implicit operator T(DynamicViewModel<T> obj) => obj._obj;
|
|
|
|
private static ViewModel CreateProxy(T obj)
|
|
{
|
|
if (DynamicViewModel.Proxies.TryGetValue(typeof(T), out var proxy))
|
|
return (ViewModel)Activator.CreateInstance(proxy, obj);
|
|
|
|
var viewModelSetMethod = AccessTools.GetDeclaredMethods(typeof(ViewModel))
|
|
.First(b => b.Name == "SetValue" && b.GetParameters()[0].ParameterType.IsByRef);
|
|
|
|
DynamicViewModel.ModuleBuilder ??=
|
|
AssemblyBuilder.DefineDynamicAssembly(new("Torch.Server.ViewModels.Generated"), AssemblyBuilderAccess.Run)
|
|
.DefineDynamicModule("Torch.Server.ViewModels.Generated");
|
|
|
|
var type = DynamicViewModel.ModuleBuilder.DefineType($"{typeof(T).FullName}ViewModel",
|
|
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, typeof(ViewModel));
|
|
|
|
var instanceField = type.DefineField("_instance", typeof(T), FieldAttributes.Private | FieldAttributes.InitOnly);
|
|
|
|
var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, [typeof(T)]);
|
|
{
|
|
var il = ctor.GetILGenerator();
|
|
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
il.Emit(OpCodes.Ldarg_1);
|
|
il.Emit(OpCodes.Stfld, instanceField);
|
|
il.Emit(OpCodes.Ret);
|
|
}
|
|
|
|
foreach (var field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public))
|
|
{
|
|
var prop = type.DefineProperty(field.Name, PropertyAttributes.None, field.FieldType, null);
|
|
|
|
foreach (var customAttribute in field.GetCustomAttributesData())
|
|
{
|
|
var hasCustomArgs = customAttribute.NamedArguments?.Any() ?? false;
|
|
var customArgsField = hasCustomArgs && customAttribute.NamedArguments[0].IsField;
|
|
|
|
var constructorArgs = customAttribute.ConstructorArguments.Select(b => b.Value).ToArray();
|
|
|
|
CustomAttributeBuilder attributeBuilder;
|
|
if (!hasCustomArgs)
|
|
attributeBuilder = new CustomAttributeBuilder(customAttribute.Constructor, constructorArgs);
|
|
else if (customArgsField)
|
|
attributeBuilder = new CustomAttributeBuilder(customAttribute.Constructor, constructorArgs,
|
|
customAttribute.NamedArguments.Select(b => (FieldInfo)b.MemberInfo).ToArray(),
|
|
customAttribute.NamedArguments.Select(b => b.TypedValue.Value).ToArray());
|
|
else
|
|
attributeBuilder = new CustomAttributeBuilder(customAttribute.Constructor, constructorArgs,
|
|
customAttribute.NamedArguments.Select(b => (PropertyInfo)b.MemberInfo).ToArray(),
|
|
customAttribute.NamedArguments.Select(b => b.TypedValue.Value).ToArray());
|
|
|
|
prop.SetCustomAttribute(attributeBuilder);
|
|
}
|
|
|
|
var getMethod = type.DefineMethod($"get_{field.Name}", MethodAttributes.Public | MethodAttributes.HideBySig, field.FieldType, Type.EmptyTypes);
|
|
{
|
|
var il = getMethod.GetILGenerator();
|
|
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
il.Emit(OpCodes.Ldfld, instanceField);
|
|
il.Emit(OpCodes.Ldfld, field);
|
|
il.Emit(OpCodes.Ret);
|
|
}
|
|
prop.SetGetMethod(getMethod);
|
|
|
|
if (field.IsInitOnly) continue;
|
|
|
|
var setMethod = type.DefineMethod($"set_{field.Name}", MethodAttributes.Public | MethodAttributes.HideBySig, typeof(void),
|
|
[field.FieldType]);
|
|
{
|
|
var il = setMethod.GetILGenerator();
|
|
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
il.Emit(OpCodes.Dup);
|
|
il.Emit(OpCodes.Ldfld, instanceField);
|
|
il.Emit(OpCodes.Ldflda, field);
|
|
il.Emit(OpCodes.Ldarg_1);
|
|
il.Emit(OpCodes.Ldstr, field.Name);
|
|
il.Emit(OpCodes.Callvirt, viewModelSetMethod.MakeGenericMethod(field.FieldType));
|
|
il.Emit(OpCodes.Ret);
|
|
}
|
|
|
|
prop.SetSetMethod(setMethod);
|
|
}
|
|
|
|
proxy = type.CreateType();
|
|
|
|
DynamicViewModel.Proxies[typeof(T)] = proxy;
|
|
|
|
return (ViewModel)Activator.CreateInstance(proxy, obj);
|
|
}
|
|
}
|
|
|
|
file static class DynamicViewModel
|
|
{
|
|
public static readonly Dictionary<Type, Type> Proxies = [];
|
|
[CanBeNull] public static ModuleBuilder ModuleBuilder;
|
|
} |