From e9dd0b0fabd337872e38acea5a4b9c711de5be9c Mon Sep 17 00:00:00 2001 From: Pavlos Touboulidis Date: Sat, 12 Jul 2014 02:45:46 +0300 Subject: [PATCH] Make FieldLoader/FieldSaver able to work with private fields * Unify the FieldLoader-related attributes (Ignore, LoadUsing, FieldFromYamlKey) into a new 'Serialize' attribute. Keep the previous attributes as shortcuts/aliases to the new attribute. * Add 'YamlName' field to the new 'Serialize' attribute to allow yaml names different from the field names. Example: [Serialize(YamlName = "Width")] private int widthExpression = "0"; * The FieldLoader will treat private fields like public now and will try to load their values. The FieldSaver gets an optional parameter specifying whether to save private fields by default. The 'Serialize' attribute can be used to fine-tune individual fields. * Updated the traits documentation exporter to write the yaml name if it has been overriden. --- OpenRA.Game/FieldLoader.cs | 167 +++++++++++++++++++++++++------------ OpenRA.Game/FieldSaver.cs | 21 +++-- OpenRA.Utility/Command.cs | 14 ++-- 3 files changed, 134 insertions(+), 68 deletions(-) diff --git a/OpenRA.Game/FieldLoader.cs b/OpenRA.Game/FieldLoader.cs index ca3615d7c8..4bacabc8b1 100644 --- a/OpenRA.Game/FieldLoader.cs +++ b/OpenRA.Game/FieldLoader.cs @@ -34,38 +34,44 @@ namespace OpenRA public static void Load(object self, MiniYaml my) { - var loadDict = typeLoadInfo[self.GetType()]; + var loadInfo = typeLoadInfo[self.GetType()]; - foreach (var kv in loadDict) + Dictionary md = null; + + foreach (var fli in loadInfo) { object val; - if (kv.Value != null) - val = kv.Value(kv.Key.Name, kv.Key.FieldType, my); - else if (!TryGetValueFromYaml(kv.Key, my, out val)) - continue; - kv.Key.SetValue(self, val); + if (fli.Loader != null) + val = fli.Loader(my); + else + { + if (md == null) + md = my.ToDictionary(); + + if (!TryGetValueFromYaml(fli.YamlName, fli.Field, md, out val)) + continue; + } + + fli.Field.SetValue(self, val); } } - static bool TryGetValueFromYaml(FieldInfo field, MiniYaml yaml, out object ret) + static bool TryGetValueFromYaml(string yamlName, FieldInfo field, Dictionary md, out object ret) { ret = null; - var n = yaml.Nodes.Where(x => x.Key == field.Name).ToList(); - if (n.Count == 0) + + MiniYaml yaml; + if (!md.TryGetValue(yamlName, out yaml)) return false; - if (n.Count == 1 && n[0].Value.Nodes.Count == 0) + + if (yaml.Nodes.Count == 0) { - ret = GetValue(field.Name, field.FieldType, n[0].Value.Value, field); + ret = GetValue(field.Name, field.FieldType, yaml.Value, field); return true; } - else if (n.Count > 1) - { - throw new InvalidOperationException("The field {0} has multiple definitions:\n{1}" - .F(field.Name, n.Select(m => "\t- " + m.Location).JoinWith("\n"))); - } - throw new InvalidOperationException("TryGetValueFromYaml: unable to load field {0} (of type {1})".F(field.Name, field.FieldType)); + throw new InvalidOperationException("TryGetValueFromYaml: unable to load field {0} (of type {1})".F(yamlName, field.FieldType)); } public static T Load(MiniYaml y) where T : new() @@ -76,27 +82,31 @@ namespace OpenRA } static readonly object[] NoIndexes = { }; - public static void LoadField(object self, string key, string value) + public static void LoadField(object target, string key, string value) { - var field = self.GetType().GetField(key.Trim()); + const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + key = key.Trim(); + + var field = target.GetType().GetField(key, flags); if (field != null) { - if (!field.HasAttribute()) - field.SetValue(self, GetValue(field.Name, field.FieldType, value, field)); + var sa = field.GetCustomAttributes(false).DefaultIfEmpty(SerializeAttribute.Default).First(); + if (!sa.FromYamlKey) + field.SetValue(target, GetValue(field.Name, field.FieldType, value, field)); return; } - var prop = self.GetType().GetProperty(key.Trim()); - + var prop = target.GetType().GetProperty(key, flags); if (prop != null) { - if (!prop.HasAttribute()) - prop.SetValue(self, GetValue(prop.Name, prop.PropertyType, value, prop), NoIndexes); + var sa = prop.GetCustomAttributes(false).DefaultIfEmpty(SerializeAttribute.Default).First(); + if (!sa.FromYamlKey) + prop.SetValue(target, GetValue(prop.Name, prop.PropertyType, value, prop), NoIndexes); return; } - UnknownFieldAction(key.Trim(), self.GetType()); + UnknownFieldAction(key, target.GetType()); } public static T GetValue(string field, string value) @@ -378,49 +388,95 @@ namespace OpenRA return InvalidValueAction(p, fieldType, field); } - static Cache>> typeLoadInfo = new Cache>>(GetTypeLoadInfo); - - static Dictionary> GetTypeLoadInfo(Type type) + public sealed class FieldLoadInfo { - var ret = new Dictionary>(); + public readonly FieldInfo Field; + public readonly SerializeAttribute Attribute; + public readonly string YamlName; + public readonly Func Loader; - foreach (var ff in type.GetFields()) + internal FieldLoadInfo(FieldInfo field, SerializeAttribute attr, string yamlName, Func loader = null) + { + Field = field; + Attribute = attr; + YamlName = yamlName; + Loader = loader; + } + } + + public static IEnumerable GetTypeLoadInfo(Type type, bool includePrivateByDefault = false) + { + return typeLoadInfo[type].Where(fli => includePrivateByDefault || fli.Field.IsPublic || (fli.Attribute.Serialize && !fli.Attribute.IsDefault)); + } + + static Cache> typeLoadInfo = new Cache>(BuildTypeLoadInfo); + + static List BuildTypeLoadInfo(Type type) + { + var ret = new List(); + + foreach (var ff in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { var field = ff; - var ignore = field.GetCustomAttributes(false); - var loadUsing = field.GetCustomAttributes(false); - var fromYamlKey = field.GetCustomAttributes(false); - if (loadUsing.Length != 0) - ret[field] = (_1, fieldType, yaml) => loadUsing[0].LoaderFunc(field)(yaml); - else if (fromYamlKey.Length != 0) - ret[field] = (f, ft, yaml) => GetValue(f, ft, yaml.Value, field); - else if (ignore.Length == 0) - ret[field] = null; + + var sa = field.GetCustomAttributes(false).DefaultIfEmpty(SerializeAttribute.Default).First(); + if (!sa.Serialize) + continue; + + var yamlName = string.IsNullOrEmpty(sa.YamlName) ? field.Name : sa.YamlName; + + var loader = sa.GetLoader(type); + if (loader == null && sa.FromYamlKey) + loader = (yaml) => GetValue(yamlName, field.FieldType, yaml.Value, field); + + var fli = new FieldLoadInfo(field, sa, yamlName, loader); + ret.Add(fli); } return ret; } [AttributeUsage(AttributeTargets.Field)] - public sealed class IgnoreAttribute : Attribute { } + public sealed class IgnoreAttribute : SerializeAttribute + { + public IgnoreAttribute() + : base(false) { } + } [AttributeUsage(AttributeTargets.Field)] - public sealed class LoadUsingAttribute : Attribute + public sealed class LoadUsingAttribute : SerializeAttribute { - Func loaderFuncCache; - public readonly string Loader; - public LoadUsingAttribute(string loader) { Loader = loader; } + } - internal Func LoaderFunc(FieldInfo field) + [AttributeUsage(AttributeTargets.Field)] + public class SerializeAttribute : Attribute + { + public static readonly SerializeAttribute Default = new SerializeAttribute(true); + + public bool IsDefault { get { return this == Default; } } + + public readonly bool Serialize; + public string YamlName; + public string Loader; + public bool FromYamlKey; + + public SerializeAttribute(bool serialize = true) { - const BindingFlags BindingFlag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; - if (loaderFuncCache == null) - loaderFuncCache = (Func)Delegate.CreateDelegate(typeof(Func), field.DeclaringType.GetMethod(Loader, BindingFlag)); - return loaderFuncCache; + Serialize = serialize; + } + + internal Func GetLoader(Type type) + { + const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; + + if (!string.IsNullOrEmpty(Loader)) + return (Func)Delegate.CreateDelegate(typeof(Func), type.GetMethod(Loader, flags)); + + return null; } } @@ -442,7 +498,14 @@ namespace OpenRA [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class TranslateAttribute : Attribute { } - public sealed class FieldFromYamlKeyAttribute : Attribute { } + [AttributeUsage(AttributeTargets.Field)] + public sealed class FieldFromYamlKeyAttribute : FieldLoader.SerializeAttribute + { + public FieldFromYamlKeyAttribute() + { + FromYamlKey = true; + } + } // mirrors DescriptionAttribute from System.ComponentModel but we dont want to have to use that everywhere. public sealed class DescAttribute : Attribute diff --git a/OpenRA.Game/FieldSaver.cs b/OpenRA.Game/FieldSaver.cs index 53da76c6b6..3c401870c7 100644 --- a/OpenRA.Game/FieldSaver.cs +++ b/OpenRA.Game/FieldSaver.cs @@ -19,31 +19,34 @@ namespace OpenRA { public static class FieldSaver { - public static MiniYaml Save(object o) + public static MiniYaml Save(object o, bool includePrivateByDefault = false) { var nodes = new List(); string root = null; - foreach (var f in o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance)) + foreach (var info in FieldLoader.GetTypeLoadInfo(o.GetType(), includePrivateByDefault)) { - if (f.HasAttribute()) - root = FormatValue(o, f); + if (info.Attribute.FromYamlKey) + root = FormatValue(o, info.Field); else - nodes.Add(new MiniYamlNode(f.Name, FormatValue(o, f))); + nodes.Add(new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))); } return new MiniYaml(root, nodes); } - public static MiniYaml SaveDifferences(object o, object from) + public static MiniYaml SaveDifferences(object o, object from, bool includePrivateByDefault = false) { if (o.GetType() != from.GetType()) throw new InvalidOperationException("FieldLoader: can't diff objects of different types"); - var fields = o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance) - .Where(f => FormatValue(o, f) != FormatValue(from, f)); + var fields = FieldLoader.GetTypeLoadInfo(o.GetType(), includePrivateByDefault) + .Where(info => FormatValue(o, info.Field) != FormatValue(from, info.Field)); - return new MiniYaml(null, fields.Select(f => new MiniYamlNode(f.Name, FormatValue(o, f))).ToList()); + return new MiniYaml( + null, + fields.Select(info => new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))).ToList() + ); } public static MiniYamlNode SaveField(object o, string field) diff --git a/OpenRA.Utility/Command.cs b/OpenRA.Utility/Command.cs index 907471c3e9..2f3f747ea5 100644 --- a/OpenRA.Utility/Command.cs +++ b/OpenRA.Utility/Command.cs @@ -320,18 +320,18 @@ namespace OpenRA.Utility foreach (var line in traitDescLines) doc.AppendLine(line); - var fields = t.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); - if (!fields.Any()) + var infos = FieldLoader.GetTypeLoadInfo(t); + if (!infos.Any()) continue; doc.AppendLine(""); doc.AppendLine(""); var liveTraitInfo = Game.modData.ObjectCreator.CreateBasic(t); - foreach (var f in fields) + foreach (var info in infos) { - var fieldDescLines = f.GetCustomAttributes(true).SelectMany(d => d.Lines); - var fieldType = FriendlyTypeName(f.FieldType); - var defaultValue = FieldSaver.SaveField(liveTraitInfo, f.Name).Value.Value; - doc.Append("".F(f.Name, defaultValue, fieldType)); + var fieldDescLines = info.Field.GetCustomAttributes(true).SelectMany(d => d.Lines); + var fieldType = FriendlyTypeName(info.Field.FieldType); + var defaultValue = FieldSaver.SaveField(liveTraitInfo, info.Field.Name).Value.Value; + doc.Append("".F(info.YamlName, defaultValue, fieldType)); doc.Append("
PropertyDefault ValueTypeDescription
{0}{1}{2}
{0}{1}{2}"); foreach (var line in fieldDescLines) doc.Append(line + " ");