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 + " ");