Browse Source

Added INotifyPropertyChanged support

4.0.0-beta
Anairkoen Schno 4 years ago
parent
commit
7e2f34d5f2
2 changed files with 212 additions and 31 deletions
  1. +8
    -0
      IPA.Loader/Config/Stores/Attributes.cs
  2. +204
    -31
      IPA.Loader/Config/Stores/GeneratedStore.cs

+ 8
- 0
IPA.Loader/Config/Stores/Attributes.cs View File

@ -1,11 +1,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace IPA.Config.Stores.Attributes namespace IPA.Config.Stores.Attributes
{ {
/// <summary>
/// Indicates that the generated subclass of the attribute's target should implement <see cref="INotifyPropertyChanged"/>.
/// If the type this is applied to already inherits it, this is implied.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class NotifyPropertyChangesAttribute : Attribute { }
/// <summary> /// <summary>
/// Causes a field or property in an object being wrapped by <see cref="GeneratedExtension.Generated{T}(Config, bool)"/> to be /// Causes a field or property in an object being wrapped by <see cref="GeneratedExtension.Generated{T}(Config, bool)"/> to be
/// ignored during serialization and deserialization. /// ignored during serialization and deserialization.


+ 204
- 31
IPA.Loader/Config/Stores/GeneratedStore.cs View File

@ -15,11 +15,12 @@ using System.IO;
using Boolean = IPA.Config.Data.Boolean; using Boolean = IPA.Config.Data.Boolean;
using System.Collections; using System.Collections;
using IPA.Utilities; using IPA.Utilities;
using System.ComponentModel;
#if NET3 #if NET3
using Net3_Proxy; using Net3_Proxy;
using Array = Net3_Proxy.Array; using Array = Net3_Proxy.Array;
#endif #endif
[assembly: InternalsVisibleTo(IPA.Config.Stores.GeneratedExtension.AssemblyVisibilityTarget)] [assembly: InternalsVisibleTo(IPA.Config.Stores.GeneratedExtension.AssemblyVisibilityTarget)]
namespace IPA.Config.Stores namespace IPA.Config.Stores
@ -84,6 +85,11 @@ namespace IPA.Config.Stores
/// <see cref="IDisposable.Dispose"/> called <i>after</i> <c>Changed()</c> is called, but <i>before</i> the write lock is released. /// <see cref="IDisposable.Dispose"/> called <i>after</i> <c>Changed()</c> is called, but <i>before</i> the write lock is released.
/// Unless you have a very good reason to use the nested <see cref="IDisposable"/>, avoid it. /// Unless you have a very good reason to use the nested <see cref="IDisposable"/>, avoid it.
/// </para> /// </para>
/// <para>
/// If <typeparamref name="T"/> is marked with <see cref="NotifyPropertyChangesAttribute"/>, the resulting object will implement
/// <see cref="INotifyPropertyChanged"/>. Similarly, if <typeparamref name="T"/> implements <see cref="INotifyPropertyChanged"/>,
/// the resulting object will implement it and notify it too.
/// </para>
/// </remarks> /// </remarks>
/// <typeparam name="T">the type to wrap</typeparam> /// <typeparam name="T">the type to wrap</typeparam>
/// <param name="cfg">the <see cref="Config"/> to register to</param> /// <param name="cfg">the <see cref="Config"/> to register to</param>
@ -121,6 +127,10 @@ namespace IPA.Config.Stores
{ {
void CopyFrom(T source, bool useLock); void CopyFrom(T source, bool useLock);
} }
internal interface IGeneratedPropertyChanged : INotifyPropertyChanged
{
PropertyChangedEventHandler PropertyChangedEvent { get; }
}
internal class Impl : IConfigStore internal class Impl : IConfigStore
{ {
@ -175,7 +185,7 @@ namespace IPA.Config.Stores
internal static MethodInfo ImplChangeTransactionMethod = typeof(Impl).GetMethod(nameof(ImplChangeTransaction)); internal static MethodInfo ImplChangeTransactionMethod = typeof(Impl).GetMethod(nameof(ImplChangeTransaction));
public static IDisposable ImplChangeTransaction(IGeneratedStore s, IDisposable nest) => FindImpl(s).ChangeTransaction(nest); public static IDisposable ImplChangeTransaction(IGeneratedStore s, IDisposable nest) => FindImpl(s).ChangeTransaction(nest);
// TODO: improve trasactionals so they don't always save in every case // TODO: improve trasactionals so they don't always save in every case
public IDisposable ChangeTransaction(IDisposable nest, bool takeWrite = true)
public IDisposable ChangeTransaction(IDisposable nest, bool takeWrite = true)
=> GetFreeTransaction().InitWith(this, !inChangeTransaction, nest, takeWrite && !WriteSyncObject.IsWriteLockHeld); => GetFreeTransaction().InitWith(this, !inChangeTransaction, nest, takeWrite && !WriteSyncObject.IsWriteLockHeld);
private ChangeTransactionObj GetFreeTransaction() private ChangeTransactionObj GetFreeTransaction()
@ -374,6 +384,7 @@ namespace IPA.Config.Stores
#region Parse base object structure #region Parse base object structure
const BindingFlags overrideMemberFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; const BindingFlags overrideMemberFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
var baseChanged = type.GetMethod("Changed", overrideMemberFlags, null, Type.EmptyTypes, Array.Empty<ParameterModifier>()); var baseChanged = type.GetMethod("Changed", overrideMemberFlags, null, Type.EmptyTypes, Array.Empty<ParameterModifier>());
if (baseChanged != null && IsMethodInvalid(baseChanged, typeof(void))) baseChanged = null; if (baseChanged != null && IsMethodInvalid(baseChanged, typeof(void))) baseChanged = null;
@ -386,6 +397,9 @@ namespace IPA.Config.Stores
var baseChangeTransaction = type.GetMethod("ChangeTransaction", overrideMemberFlags, null, Type.EmptyTypes, Array.Empty<ParameterModifier>()); var baseChangeTransaction = type.GetMethod("ChangeTransaction", overrideMemberFlags, null, Type.EmptyTypes, Array.Empty<ParameterModifier>());
if (baseChangeTransaction != null && IsMethodInvalid(baseChangeTransaction, typeof(IDisposable))) baseChangeTransaction = null; if (baseChangeTransaction != null && IsMethodInvalid(baseChangeTransaction, typeof(IDisposable))) baseChangeTransaction = null;
var isINotifyPropertyChanged = type.FindInterfaces((i, t) => i == (Type)t, typeof(INotifyPropertyChanged)).Length != 0;
var hasNotifyAttribute = type.GetCustomAttribute<NotifyPropertyChangesAttribute>() != null;
var structure = new List<SerializedMemberInfo>(); var structure = new List<SerializedMemberInfo>();
bool ProcessAttributesFor(ref SerializedMemberInfo member) bool ProcessAttributesFor(ref SerializedMemberInfo member)
@ -597,6 +611,135 @@ namespace IPA.Config.Stores
const MethodAttributes propertyMethodAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; const MethodAttributes propertyMethodAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
const MethodAttributes virtualPropertyMethodAttr = propertyMethodAttr | MethodAttributes.Virtual | MethodAttributes.Final; const MethodAttributes virtualPropertyMethodAttr = propertyMethodAttr | MethodAttributes.Virtual | MethodAttributes.Final;
const MethodAttributes virtualMemberMethod = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Final; const MethodAttributes virtualMemberMethod = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig | MethodAttributes.Final;
#region INotifyPropertyChanged
MethodBuilder notifyChanged = null;
if (isINotifyPropertyChanged || hasNotifyAttribute)
{
var INotifyPropertyChanged_t = typeof(INotifyPropertyChanged);
typeBuilder.AddInterfaceImplementation(INotifyPropertyChanged_t);
var INotifyPropertyChanged_PropertyChanged =
INotifyPropertyChanged_t.GetEvent(nameof(INotifyPropertyChanged.PropertyChanged));
var PropertyChangedEventHandler_t = typeof(PropertyChangedEventHandler);
var PropertyChangedEventHander_Invoke = PropertyChangedEventHandler_t.GetMethod(nameof(PropertyChangedEventHandler.Invoke));
var PropertyChangedEventArgs_t = typeof(PropertyChangedEventArgs);
var PropertyChangedEventArgs_ctor = PropertyChangedEventArgs_t.GetConstructor(new[] { typeof(string) });
var Delegate_t = typeof(Delegate);
var Delegate_Combine = Delegate_t.GetMethod(nameof(Delegate.Combine), BindingFlags.Static | BindingFlags.Public, null,
new[] { Delegate_t, Delegate_t }, Array.Empty<ParameterModifier>());
var Delegate_Remove = Delegate_t.GetMethod(nameof(Delegate.Remove), BindingFlags.Static | BindingFlags.Public, null,
new[] { Delegate_t, Delegate_t }, Array.Empty<ParameterModifier>());
var CompareExchange = typeof(Interlocked).GetMethods()
.Where(m => m.Name == nameof(Interlocked.CompareExchange))
.Where(m => m.ContainsGenericParameters)
.Where(m => m.GetParameters().Length == 3).First()
.MakeGenericMethod(PropertyChangedEventHandler_t);
var PropertyChanged_backing = typeBuilder.DefineField("<event>PropertyChanged", PropertyChangedEventHandler_t, FieldAttributes.Private);
var add_PropertyChanged = typeBuilder.DefineMethod("<add>PropertyChanged",
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Final | MethodAttributes.Virtual,
null, new[] { PropertyChangedEventHandler_t });
typeBuilder.DefineMethodOverride(add_PropertyChanged, INotifyPropertyChanged_PropertyChanged.GetAddMethod());
{
var il = add_PropertyChanged.GetILGenerator();
var loopLabel = il.DefineLabel();
var delTemp = il.DeclareLocal(PropertyChangedEventHandler_t);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, PropertyChanged_backing);
il.MarkLabel(loopLabel);
il.Emit(OpCodes.Stloc, delTemp);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldflda, PropertyChanged_backing);
il.Emit(OpCodes.Ldloc, delTemp);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, Delegate_Combine);
il.Emit(OpCodes.Castclass, PropertyChangedEventHandler_t);
il.Emit(OpCodes.Ldloc, delTemp);
il.Emit(OpCodes.Call, CompareExchange);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldloc, delTemp);
il.Emit(OpCodes.Bne_Un_S, loopLabel);
il.Emit(OpCodes.Ret);
}
var remove_PropertyChanged = typeBuilder.DefineMethod("<remove>PropertyChanged",
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Final | MethodAttributes.Virtual,
null, new[] { PropertyChangedEventHandler_t });
typeBuilder.DefineMethodOverride(remove_PropertyChanged, INotifyPropertyChanged_PropertyChanged.GetRemoveMethod());
{
var il = remove_PropertyChanged.GetILGenerator();
var loopLabel = il.DefineLabel();
var delTemp = il.DeclareLocal(PropertyChangedEventHandler_t);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, PropertyChanged_backing);
il.MarkLabel(loopLabel);
il.Emit(OpCodes.Stloc, delTemp);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldflda, PropertyChanged_backing);
il.Emit(OpCodes.Ldloc, delTemp);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, Delegate_Remove);
il.Emit(OpCodes.Castclass, PropertyChangedEventHandler_t);
il.Emit(OpCodes.Ldloc, delTemp);
il.Emit(OpCodes.Call, CompareExchange);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldloc, delTemp);
il.Emit(OpCodes.Bne_Un_S, loopLabel);
il.Emit(OpCodes.Ret);
}
var PropertyChanged_event = typeBuilder.DefineEvent(nameof(INotifyPropertyChanged.PropertyChanged), EventAttributes.None, PropertyChangedEventHandler_t);
PropertyChanged_event.SetAddOnMethod(add_PropertyChanged);
PropertyChanged_event.SetRemoveOnMethod(remove_PropertyChanged);
notifyChanged = typeBuilder.DefineMethod("<>NotifyChanged",
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Final, null, new[] { typeof(string) });
{
var il = notifyChanged.GetILGenerator();
var invokeNonNull = il.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, PropertyChanged_backing);
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Brtrue, invokeNonNull);
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ret);
il.MarkLabel(invokeNonNull);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Newobj, PropertyChangedEventArgs_ctor);
il.Emit(OpCodes.Call, PropertyChangedEventHander_Invoke);
il.Emit(OpCodes.Ret);
}
}
#endregion
#region IGeneratedStore #region IGeneratedStore
typeBuilder.AddInterfaceImplementation(typeof(IGeneratedStore)); typeBuilder.AddInterfaceImplementation(typeof(IGeneratedStore));
@ -765,8 +908,18 @@ namespace IPA.Config.Stores
EmitDeserializeMember(il, member, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal); EmitDeserializeMember(il, member, nextLabel, il => il.Emit(OpCodes.Ldloc_S, valueLocal), GetLocal);
} }
il.MarkLabel(nextLabel);
il.MarkLabel(nextLabel);
if (notifyChanged != null)
{
foreach (var member in structure)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, member.Name);
il.Emit(OpCodes.Call, notifyChanged);
}
}
il.Emit(OpCodes.Ret); il.Emit(OpCodes.Ret);
} }
#endregion #endregion
@ -935,6 +1088,16 @@ namespace IPA.Config.Stores
il.EndExceptionBlock(); il.EndExceptionBlock();
} }
if (notifyChanged != null)
{
foreach (var member in structure)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, member.Name);
il.Emit(OpCodes.Call, notifyChanged);
}
}
var endLock = il.DefineLabel(); var endLock = il.DefineLabel();
il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Brfalse, endLock); il.Emit(OpCodes.Brfalse, endLock);
@ -975,7 +1138,7 @@ namespace IPA.Config.Stores
il.Emit(OpCodes.Ret); il.Emit(OpCodes.Ret);
} }
} }
#endregion
#endregion
#region Members #region Members
foreach (var member in structure.Where(m => m.IsVirtual)) foreach (var member in structure.Where(m => m.IsVirtual))
@ -1042,6 +1205,12 @@ namespace IPA.Config.Stores
il.EndExceptionBlock(); il.EndExceptionBlock();
if (notifyChanged != null)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, member.Name);
il.Emit(OpCodes.Call, notifyChanged);
}
il.Emit(OpCodes.Ret); il.Emit(OpCodes.Ret);
} }
@ -1056,28 +1225,9 @@ namespace IPA.Config.Stores
).Compile(); ).Compile();
return (creatorDel, genType); return (creatorDel, genType);
}
private delegate LocalBuilder GetLocal(Type type, int idx = 0);
private static GetLocal MakeGetLocal(ILGenerator il)
{ // TODO: improve this shit a bit so that i can release a hold of a variable and do more auto managing
var locals = new List<LocalBuilder>();
LocalBuilder GetLocal(Type ty, int i = 0)
{
var builder = locals.Where(b => b.LocalType == ty).Skip(i).FirstOrDefault();
if (builder == null)
{
builder = il.DeclareLocal(ty);
locals.Add(builder);
}
return builder;
}
return GetLocal;
}
}
#region Logs
private static readonly MethodInfo LogErrorMethod = typeof(GeneratedStore).GetMethod(nameof(LogError), BindingFlags.NonPublic | BindingFlags.Static); private static readonly MethodInfo LogErrorMethod = typeof(GeneratedStore).GetMethod(nameof(LogError), BindingFlags.NonPublic | BindingFlags.Static);
internal static void LogError(Type expected, Type found, string message) internal static void LogError(Type expected, Type found, string message)
{ {
@ -1092,9 +1242,10 @@ namespace IPA.Config.Stores
internal static void LogWarningException(Exception exception) internal static void LogWarningException(Exception exception)
{ {
Logger.config.Warn(exception); Logger.config.Warn(exception);
}
}
#endregion
#region Correction
private static bool NeedsCorrection(SerializedMemberInfo member) private static bool NeedsCorrection(SerializedMemberInfo member)
{ {
var expectType = GetExpectedValueTypeForType(member.IsNullable ? member.NullableWrappedType : member.Type); var expectType = GetExpectedValueTypeForType(member.IsNullable ? member.NullableWrappedType : member.Type);
@ -1155,9 +1306,31 @@ namespace IPA.Config.Stores
il.Emit(OpCodes.Newobj, member.Nullable_Construct); il.Emit(OpCodes.Newobj, member.Nullable_Construct);
il.MarkLabel(endLabel); il.MarkLabel(endLabel);
}
#endregion
#region Utility
private delegate LocalBuilder GetLocal(Type type, int idx = 0);
private static GetLocal MakeGetLocal(ILGenerator il)
{ // TODO: improve this shit a bit so that i can release a hold of a variable and do more auto managing
var locals = new List<LocalBuilder>();
LocalBuilder GetLocal(Type ty, int i = 0)
{
var builder = locals.Where(b => b.LocalType == ty).Skip(i).FirstOrDefault();
if (builder == null)
{
builder = il.DeclareLocal(ty);
locals.Add(builder);
}
return builder;
}
return GetLocal;
} }
#region Utility
private static void EmitLoad(ILGenerator il, SerializedMemberInfo member, Action<ILGenerator> thisarg = null) private static void EmitLoad(ILGenerator il, SerializedMemberInfo member, Action<ILGenerator> thisarg = null)
{ {
if (thisarg == null) if (thisarg == null)


Loading…
Cancel
Save