Browse Source

Added Attributes ant their support

pull/46/head
Anairkoen Schno 4 years ago
parent
commit
924b66d516
4 changed files with 148 additions and 15 deletions
  1. +7
    -1
      IPA.Loader/Config/SelfConfig.cs
  2. +68
    -0
      IPA.Loader/Config/Stores/Attributes.cs
  3. +72
    -14
      IPA.Loader/Config/Stores/GeneratedStore.cs
  4. +1
    -0
      IPA.Loader/Loader/DisabledConfig.cs

+ 7
- 1
IPA.Loader/Config/SelfConfig.cs View File

@ -4,6 +4,7 @@ using IPA.Utilities;
using IPA.Config.Stores;
// END: section ignore
using Newtonsoft.Json;
using IPA.Config.Stores.Attributes;
namespace IPA.Config
{
@ -79,6 +80,9 @@ namespace IPA.Config
&& CommandLineValues.Updates.AutoCheckUpdates;
}
// LINE: ignore
[NonNullable]
[JsonProperty(Required = Required.DisallowNull)] // the JsonProperty annotations are for the generated schema
public virtual Updates_ Updates { get; set; } = new Updates_();
public class Debug_
@ -117,6 +121,9 @@ namespace IPA.Config
|| CommandLineValues.Debug.ShowTrace;
}
// LINE: ignore
[NonNullable]
[JsonProperty(Required = Required.DisallowNull)]
public virtual Debug_ Debug { get; set; } = new Debug_();
public virtual bool YeetMods { get; set; } = true;
@ -124,7 +131,6 @@ namespace IPA.Config
public static bool YeetMods_ => (Instance?.YeetMods ?? true)
&& CommandLineValues.YeetMods;
[JsonProperty(Required = Required.Default)]
public virtual string LastGameVersion { get; set; } = null;
// LINE: ignore
public static string LastGameVersion_ => Instance?.LastGameVersion;


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

@ -6,5 +6,73 @@ using System.Threading.Tasks;
namespace IPA.Config.Stores.Attributes
{
/// <summary>
/// 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.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class IgnoreAttribute : Attribute { }
/// <summary>
/// Indicates that a field or property in an object being wrapped by <see cref="GeneratedExtension.Generated{T}(Config, bool)"/>
/// that would otherwise be nullable (i.e. a reference type or a <see cref="Nullable{T}"/> type) should never be null, and the
/// member will be ignored if the deserialized value is <see langword="null"/>.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class NonNullableAttribute : Attribute { }
/// <summary>
/// Specifies a name for the serialized field or property in an object being wrapped by
/// <see cref="GeneratedExtension.Generated{T}(Config, bool)"/> that is different from the member name itself.
/// </summary>
/// <example>
/// <para>
/// When serializing the following object, we might get the JSON that follows.
/// <code>
/// public class PluginConfig
/// {
/// public virtual bool BooleanField { get; set; } = true;
/// }
/// </code>
/// <code>
/// {
/// "BooleanField": true
/// }
/// </code>
/// </para>
/// <para>
/// However, if we were to add a <see cref="SerializedNameAttribute"/> to that field, we would get the following.
/// <code>
/// public class PluginConfig
/// {
/// [SerializedName("bool")]
/// public virtual bool BooleanField { get; set; } = true;
/// }
/// </code>
/// <code>
/// {
/// "bool": true
/// }
/// </code>
/// </para>
/// </example>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class SerializedNameAttribute : Attribute
{
/// <summary>
/// Gets the name to replace the member name with.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Creates a new <see cref="SerializedNameAttribute"/> with the given <see cref="Name"/>.
/// </summary>
/// <param name="name">the value to assign to <see cref="Name"/></param>
public SerializedNameAttribute(string name)
{
Name = name;
}
}
}

+ 72
- 14
IPA.Loader/Config/Stores/GeneratedStore.cs View File

@ -1,4 +1,5 @@
using IPA.Config.Data;
using IPA.Config.Stores.Attributes;
using IPA.Logging;
using System;
using System.Collections.Generic;
@ -13,12 +14,13 @@ using System.Runtime.CompilerServices;
using System.IO;
using Boolean = IPA.Config.Data.Boolean;
using System.Collections;
using IPA.Utilities;
#if NET3
using Net3_Proxy;
using Array = Net3_Proxy.Array;
#endif
[assembly: InternalsVisibleTo(IPA.Config.Stores.GeneratedStore.GeneratedAssemblyName)]
[assembly: InternalsVisibleTo(IPA.Config.Stores.GeneratedExtension.AssemblyVisibilityTarget)]
namespace IPA.Config.Stores
{
@ -48,6 +50,15 @@ namespace IPA.Config.Stores
/// </code>
/// </para>
/// <para>
/// Only fields and properties that are <see langword="public"/> or <see langword="protected"/> will be considered, and only properties
/// where both the getter and setter are <see langword="public"/> or <see langword="protected"/> are considered. Any fields or properties
/// with an <see cref="IgnoreAttribute"/> applied to them are also ignored. Having properties be <see langword="virtual"/> is not strictly
/// necessary, however it allows the generated type to keep track of changes and lock around them so that the config will auto-save.
/// </para>
/// <para>
/// All of the attributes in the <see cref="Attributes"/> namespace are handled as described by them.
/// </para>
/// <para>
/// If the <typeparamref name="T"/> declares a <see langword="public"/> or <see langword="protected"/>, <see langword="virtual"/>
/// 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
@ -222,9 +233,18 @@ namespace IPA.Config.Stores
{
public string Name;
public MemberInfo Member;
public Type Type;
public bool AllowNull;
public bool IsVirtual;
public bool IsField;
public Type Type;
public bool IsNullable;
// invalid for objects with IsNullabe false
public Type NullableWrappedType => Nullable.GetUnderlyingType(Type);
// invalid for objects with IsNullabe false
public PropertyInfo Nullable_HasValue => Type.GetProperty(nameof(Nullable<int>.HasValue));
// invalid for objects with IsNullabe false
public PropertyInfo Nullable_Value => Type.GetProperty(nameof(Nullable<int>.Value));
}
private static Func<IGeneratedStore, IConfigStore> MakeCreator(Type type)
@ -251,15 +271,48 @@ namespace IPA.Config.Stores
var structure = new List<SerializedMemberInfo>();
// TODO: incorporate attributes/base types
// TODO: ignore probs without setter
// TODO: support converters
bool ProcessAttributesFor(MemberInfo member, Type memberType, out string name, out bool allowNull, out bool isNullable)
{
var attrs = member.GetCustomAttributes(true);
var ignores = attrs.Select(o => o as IgnoreAttribute).NonNull();
if (ignores.Any()) // we ignore
{
name = null;
allowNull = false;
isNullable = false;
return false;
}
var nonNullables = attrs.Select(o => o as NonNullableAttribute).NonNull();
name = member.Name;
isNullable = memberType.IsConstructedGenericType
&& memberType.GetGenericTypeDefinition() == typeof(Nullable<>);
allowNull = !nonNullables.Any() && (!memberType.IsValueType || isNullable);
var nameAttr = attrs.Select(o => o as SerializedNameAttribute).NonNull().FirstOrDefault();
if (nameAttr != null)
name = nameAttr.Name;
return true;
}
// only looks at public properties
foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
// only looks at public/protected properties
foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (prop.GetSetMethod(true)?.IsPrivate ?? true)
{ // we enter this block if the setter is inacessible or doesn't exist
continue; // ignore props without setter
}
if (prop.GetGetMethod(true)?.IsPrivate ?? true)
{ // we enter this block if the getter is inacessible or doesn't exist
continue; // ignore props without getter
}
var smi = new SerializedMemberInfo
{
Name = prop.Name,
Member = prop,
IsVirtual = (prop.GetGetMethod(true)?.IsVirtual ?? false) ||
(prop.GetSetMethod(true)?.IsVirtual ?? false),
@ -267,21 +320,26 @@ namespace IPA.Config.Stores
Type = prop.PropertyType
};
if (!ProcessAttributesFor(smi.Member, smi.Type, out smi.Name, out smi.AllowNull, out smi.IsNullable)) continue;
structure.Add(smi);
}
// only look at public fields
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public))
// only look at public/protected fields
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (field.IsPrivate) continue;
var smi = new SerializedMemberInfo
{
Name = field.Name,
Member = field,
IsVirtual = false,
IsField = true,
Type = field.FieldType
};
if (!ProcessAttributesFor(smi.Member, smi.Type, out smi.Name, out smi.AllowNull, out smi.IsNullable)) continue;
structure.Add(smi);
}
#endregion
@ -788,18 +846,18 @@ namespace IPA.Config.Stores
}
}
// TODO: implement Nullable<T>
EmitLoad();
var endSerialize = il.DefineLabel();
if (!member.Type.IsValueType)
if (member.AllowNull)
{
var passedNull = il.DefineLabel();
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Brtrue, passedNull);
// TODO: add special check for nullables
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Br, endSerialize);
@ -972,7 +1030,7 @@ namespace IPA.Config.Stores
// TODO: support Nullable<T>
if (member.Type.IsValueType)
if (!member.AllowNull)
{
il.Emit(OpCodes.Pop);
EmitLogError(il, $"Member {member.Name} ({member.Type}) not nullable", tailcall: false,


+ 1
- 0
IPA.Loader/Loader/DisabledConfig.cs View File

@ -1,5 +1,6 @@
using IPA.Config;
using IPA.Config.Stores;
using IPA.Config.Stores.Attributes;
using IPA.Utilities;
using System;
using System.Collections.Generic;


Loading…
Cancel
Save