diff --git a/IPA.Loader/Config/SelfConfig.cs b/IPA.Loader/Config/SelfConfig.cs
index 49dd6727..a20b9116 100644
--- a/IPA.Loader/Config/SelfConfig.cs
+++ b/IPA.Loader/Config/SelfConfig.cs
@@ -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)
diff --git a/IPA.Loader/Config/Stores/GeneratedStore.cs b/IPA.Loader/Config/Stores/GeneratedStore.cs
index 29f615b0..478753b4 100644
--- a/IPA.Loader/Config/Stores/GeneratedStore.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStore.cs
@@ -63,7 +63,7 @@ namespace IPA.Config.Stores
/// method Changed(), 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?
///
///
/// Similarly, can declare a public or protected,
@@ -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 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 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($"{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($"{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 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 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 expected = null, Action 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 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 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 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 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 getValue, Func GetLocal)
+ private static void EmitDeserializeMember(ILGenerator il, SerializedMemberInfo member, Label nextLabel, Action 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 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
}