Browse Source

Added the long-awaited autosaving

4.0.0-beta
Anairkoen Schno 4 years ago
parent
commit
7e971e7ae1
2 changed files with 189 additions and 86 deletions
  1. +5
    -0
      IPA.Loader/Config/SelfConfig.cs
  2. +184
    -86
      IPA.Loader/Config/Stores/GeneratedStore.cs

+ 5
- 0
IPA.Loader/Config/SelfConfig.cs View File

@ -28,6 +28,11 @@ namespace IPA.Config
StandardLogger.Configure(this);
}
protected internal virtual void Changed()
{
Logger.log.Debug("SelfConfig Changed called");
}
public static void ReadCommandLine(string[] args)
{
foreach (var arg in args)


+ 184
- 86
IPA.Loader/Config/Stores/GeneratedStore.cs View File

@ -63,7 +63,7 @@ namespace IPA.Config.Stores
/// method <c>Changed()</c>, then that method may be called to artificially signal to the runtime that the content of the object
/// has changed. That method will also be called after the write locks are released when a property is set anywhere in the owning
/// tree. This will only be called on the outermost generated object of the config structure, even if the change being signaled
/// is somewhere deep into the tree.
/// is somewhere deep into the tree. TODO: is this a good idea?
/// </para>
/// <para>
/// Similarly, <typeparamref name="T"/> can declare a public or protected, <see langword="virtual"/>
@ -101,6 +101,8 @@ namespace IPA.Config.Stores
Impl Impl { get; }
void OnReload();
void Changed();
Value Serialize();
void Deserialize(Value val);
}
@ -121,7 +123,11 @@ namespace IPA.Config.Stores
internal static MethodInfo ImplSignalChangedMethod = typeof(Impl).GetMethod(nameof(ImplSignalChanged));
public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s).SignalChanged();
public void SignalChanged() => resetEvent.Set();
public void SignalChanged() => resetEvent.Set();
internal static MethodInfo ImplInvokeChangedMethod = typeof(Impl).GetMethod(nameof(ImplInvokeChanged));
public static void ImplInvokeChanged(IGeneratedStore s) => FindImpl(s).InvokeChanged();
public void InvokeChanged() => generated.Changed();
internal static MethodInfo ImplTakeReadMethod = typeof(Impl).GetMethod(nameof(ImplTakeRead));
public static void ImplTakeRead(IGeneratedStore s) => FindImpl(s).TakeRead();
@ -369,8 +375,12 @@ namespace IPA.Config.Stores
il.Emit(OpCodes.Newobj, Impl.Ctor);
il.Emit(OpCodes.Stfld, implField);
var GetLocal = MakeGetLocal(il);
foreach (var member in structure)
EmitMemberFix(il, member);
{
EmitMemberFix(il, member, GetLocal);
}
il.Emit(OpCodes.Pop);
@ -393,6 +403,7 @@ namespace IPA.Config.Stores
var IGeneratedStore_Serialize = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Serialize));
var IGeneratedStore_Deserialize = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Deserialize));
var IGeneratedStore_OnReload = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.OnReload));
var IGeneratedStore_Changed = IGeneratedStore_t.GetMethod(nameof(IGeneratedStore.Changed));
#region IGeneratedStore.OnReload
var onReload = typeBuilder.DefineMethod($"<>{nameof(IGeneratedStore.OnReload)}", virtualMemberMethod, null, Type.EmptyTypes);
@ -465,18 +476,7 @@ namespace IPA.Config.Stores
il.Emit(OpCodes.Call, typeof(Value).GetMethod(nameof(Value.Map)));
// the map is now at the top of the stack
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;
}
var GetLocal = MakeGetLocal(il);
foreach (var member in structure)
{
@ -531,18 +531,7 @@ namespace IPA.Config.Stores
var nextLabel = notMapError;
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;
}
var GetLocal = MakeGetLocal(il);
// head of stack is Map instance
foreach (var member in structure)
@ -650,7 +639,7 @@ namespace IPA.Config.Stores
#region Changed
var coreChanged = typeBuilder.DefineMethod(
"<>Changed",
MethodAttributes.Public | MethodAttributes.HideBySig,
virtualMemberMethod,
null, Type.EmptyTypes);
{
@ -663,8 +652,8 @@ namespace IPA.Config.Stores
if (baseChanged != null) {
var changedMethod = typeBuilder.DefineMethod( // copy to override baseChanged
baseChanged.Name,
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig,
baseChanged.Name,
virtualMemberMethod,
null, Type.EmptyTypes);
typeBuilder.DefineMethodOverride(changedMethod, baseChanged);
@ -683,10 +672,80 @@ namespace IPA.Config.Stores
coreChanged = changedMethod; // switch to calling this version instead of just the default
}
typeBuilder.DefineMethodOverride(coreChanged, IGeneratedStore_Changed);
#endregion
// TODO: generate overrides for all the virtual properties
foreach (var member in structure.Where(m => m.IsVirtual))
{ // IsVirtual implies !IsField
var prop = member.Member as PropertyInfo;
var get = prop.GetGetMethod(true);
var set = prop.GetSetMethod(true);
var propBuilder = typeBuilder.DefineProperty($"{member.Name}#", PropertyAttributes.None, member.Type, null);
var propGet = typeBuilder.DefineMethod($"<g>{propBuilder.Name}", virtualPropertyMethodAttr, member.Type, Type.EmptyTypes);
propBuilder.SetGetMethod(propGet);
typeBuilder.DefineMethodOverride(propGet, get);
{
var il = propGet.GetILGenerator();
var local = il.DeclareLocal(member.Type);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, Impl.ImplTakeReadMethod); // take the read lock
il.BeginExceptionBlock();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, get); // call base getter
il.Emit(OpCodes.Stloc, local);
il.BeginFinallyBlock();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, Impl.ImplReleaseReadMethod); // release the read lock
il.EndExceptionBlock();
il.Emit(OpCodes.Ldloc, local);
il.Emit(OpCodes.Ret);
}
var propSet = typeBuilder.DefineMethod($"<s>{propBuilder.Name}", virtualPropertyMethodAttr, null, new[] { member.Type });
propBuilder.SetSetMethod(propSet);
typeBuilder.DefineMethodOverride(propSet, set);
{ // TODO: decide if i want to correct the value before or after i take the write lock
var il = propSet.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, Impl.ImplTakeWriteMethod); // take the write lock
il.BeginExceptionBlock();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
EmitCorrectMember(il, member);
il.Emit(OpCodes.Call, set);
il.BeginFinallyBlock();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, Impl.ImplReleaseWriteMethod); // release the write lock
il.EndExceptionBlock();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, Impl.ImplInvokeChangedMethod);
il.Emit(OpCodes.Ret);
}
}
var genType = typeBuilder.CreateType();
var parentParam = Expression.Parameter(typeof(IGeneratedStore), "parent");
@ -704,7 +763,92 @@ namespace IPA.Config.Stores
return creatorDel;
}
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)
{ // TODO: pull this garbage out somewhere sane somehow
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;
}
private static readonly MethodInfo LogErrorMethod = typeof(GeneratedStore).GetMethod(nameof(LogError), BindingFlags.NonPublic | BindingFlags.Static);
internal static void LogError(Type expected, Type found, string message)
{
Logger.config.Notice($"{message}{(expected == null ? "" : $" (expected {expected}, found {found?.ToString() ?? "null"})")}");
}
private static bool NeedsCorrection(SerializedMemberInfo member)
{
return false;
}
// expects start value on stack, exits with final value on stack
private static void EmitCorrectMember(ILGenerator il, SerializedMemberInfo member)
{
if (!NeedsCorrection(member)) return;
// TODO: impl
}
// expects the this param to be on the stack
private static void EmitMemberFix(ILGenerator il, SerializedMemberInfo member, GetLocal GetLocal)
{
if (!NeedsCorrection(member)) return;
var local = GetLocal(member.Type);
EmitLoad(il, member); // load the member
EmitCorrectMember(il, member); // correct it
il.Emit(OpCodes.Stloc, local);
EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, local));
}
#region Utility
private static void EmitLoad(ILGenerator il, SerializedMemberInfo member)
{
il.Emit(OpCodes.Ldarg_0); // load this
if (member.IsField)
il.Emit(OpCodes.Ldfld, member.Member as FieldInfo);
else
{ // member is a property
var prop = member.Member as PropertyInfo;
var getter = prop.GetGetMethod();
if (getter == null) throw new InvalidOperationException($"Property {member.Name} does not have a getter and is not ignored");
il.Emit(OpCodes.Call, getter);
}
}
private static void EmitStore(ILGenerator il, SerializedMemberInfo member, Action<ILGenerator> value)
{
il.Emit(OpCodes.Ldarg_0); // load this
value(il);
if (member.IsField)
il.Emit(OpCodes.Stfld, member.Member as FieldInfo);
else
{ // member is a property
var prop = member.Member as PropertyInfo;
var setter = prop.GetSetMethod();
if (setter == null) throw new InvalidOperationException($"Property {member.Name} does not have a setter and is not ignored");
il.Emit(OpCodes.Call, setter);
}
}
private static void EmitLogError(ILGenerator il, string message, bool tailcall = false, Action<ILGenerator> expected = null, Action<ILGenerator> found = null)
{
if (expected == null) expected = il => il.Emit(OpCodes.Ldnull);
@ -814,41 +958,12 @@ namespace IPA.Config.Stores
}
#endregion
private static readonly MethodInfo LogErrorMethod = typeof(GeneratedStore).GetMethod(nameof(LogError), BindingFlags.NonPublic | BindingFlags.Static);
internal static void LogError(Type expected, Type found, string message)
{
Logger.config.Notice($"{message}{(expected == null ? "" : $" (expected {expected}, found {found?.ToString() ?? "null"})")}");
}
// expects the this param to be on the stack
private static void EmitMemberFix(ILGenerator il, SerializedMemberInfo member)
{
// TODO: impl
}
#region Serialize
// emit takes no args, leaves Value at top of stack
private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, Func<Type, int, LocalBuilder> GetLocal)
private static void EmitSerializeMember(ILGenerator il, SerializedMemberInfo member, GetLocal GetLocal)
{
void EmitLoad()
{
il.Emit(OpCodes.Ldarg_0); // load this
if (member.IsField)
il.Emit(OpCodes.Ldfld, member.Member as FieldInfo);
else
{ // member is a property
var prop = member.Member as PropertyInfo;
var getter = prop.GetGetMethod();
if (getter == null) throw new InvalidOperationException($"Property {member.Name} does not have a getter and is not ignored");
il.Emit(OpCodes.Call, getter);
}
}
EmitLoad();
EmitLoad(il, member);
var endSerialize = il.DefineLabel();
@ -901,7 +1016,7 @@ namespace IPA.Config.Stores
}
else if (targetType == typeof(List))
{
// TODO: impl this
// TODO: impl this (enumerables)
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldnull);
}
@ -948,7 +1063,7 @@ namespace IPA.Config.Stores
return typeof(Map); // default for various objects
}
private static void EmitDeserializeGeneratedValue(ILGenerator il, Type targetType, Type srcType, Func<Type, int, LocalBuilder> GetLocal)
private static void EmitDeserializeGeneratedValue(ILGenerator il, Type targetType, Type srcType, GetLocal GetLocal)
{
var IGeneratedStore_Deserialize = typeof(IGeneratedStore).GetMethod(nameof(IGeneratedStore.Deserialize));
@ -960,7 +1075,7 @@ namespace IPA.Config.Stores
il.Emit(OpCodes.Callvirt, IGeneratedStore_Deserialize);
}
private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, Func<Type, int, LocalBuilder> GetLocal)
private static void EmitDeserializeNullable(ILGenerator il, SerializedMemberInfo member, Type expected, GetLocal GetLocal)
{
EmitDeserializeValue(il, member.NullableWrappedType, expected, GetLocal);
il.Emit(OpCodes.Newobj, member.Nullable_Construct);
@ -968,7 +1083,7 @@ namespace IPA.Config.Stores
// top of stack is the Value to deserialize; the type will be as returned from GetExpectedValueTypeForType
// after, top of stack will be thing to write to field
private static void EmitDeserializeValue(ILGenerator il, Type targetType, Type expected, Func<Type, int, LocalBuilder> GetLocal)
private static void EmitDeserializeValue(ILGenerator il, Type targetType, Type expected, GetLocal GetLocal)
{
if (typeof(Value).IsAssignableFrom(targetType)) return; // do nothing
@ -1012,7 +1127,7 @@ namespace IPA.Config.Stores
}
// emit takes the value being deserialized, logs on error, leaves nothing on stack
private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action<ILGenerator> getValue, Func<Type, int, LocalBuilder> GetLocal)
private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action<ILGenerator> getValue, GetLocal GetLocal)
{
var Object_GetType = typeof(object).GetMethod(nameof(Object.GetType));
@ -1020,23 +1135,6 @@ namespace IPA.Config.Stores
var passedTypeCheck = il.DefineLabel();
var expectType = GetExpectedValueTypeForType(member.IsNullable ? member.NullableWrappedType : member.Type);
void EmitStore(Action<ILGenerator> value)
{
il.Emit(OpCodes.Ldarg_0); // load this
value(il);
if (member.IsField)
il.Emit(OpCodes.Stfld, member.Member as FieldInfo);
else
{ // member is a property
var prop = member.Member as PropertyInfo;
var setter = prop.GetSetMethod();
if (setter == null) throw new InvalidOperationException($"Property {member.Name} does not have a setter and is not ignored");
il.Emit(OpCodes.Call, setter);
}
}
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Brtrue_S, implLabel); // null check
@ -1053,13 +1151,13 @@ namespace IPA.Config.Stores
var valTLocal = GetLocal(member.Type, 0);
il.Emit(OpCodes.Ldloca, valTLocal);
il.Emit(OpCodes.Initobj, member.Type);
EmitStore(il => il.Emit(OpCodes.Ldloc, valTLocal));
EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, valTLocal));
il.Emit(OpCodes.Br, nextLabel);
}
else
{
il.Emit(OpCodes.Pop);
EmitStore(il => il.Emit(OpCodes.Ldnull));
EmitStore(il, member, il => il.Emit(OpCodes.Ldnull));
il.Emit(OpCodes.Br, nextLabel);
}
@ -1117,7 +1215,7 @@ namespace IPA.Config.Stores
if (member.IsNullable) EmitDeserializeNullable(il, member, expectType, GetLocal);
else EmitDeserializeValue(il, member.Type, expectType, GetLocal);
il.Emit(OpCodes.Stloc, local);
EmitStore(il => il.Emit(OpCodes.Ldloc, local));
EmitStore(il, member, il => il.Emit(OpCodes.Ldloc, local));
}
#endregion
}


Loading…
Cancel
Save