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.
This commit is contained in:
Pavlos Touboulidis
2014-07-12 02:45:46 +03:00
parent a357bceb35
commit e9dd0b0fab
3 changed files with 134 additions and 68 deletions

View File

@@ -34,38 +34,44 @@ namespace OpenRA
public static void Load(object self, MiniYaml my) public static void Load(object self, MiniYaml my)
{ {
var loadDict = typeLoadInfo[self.GetType()]; var loadInfo = typeLoadInfo[self.GetType()];
foreach (var kv in loadDict) Dictionary<string, MiniYaml> md = null;
foreach (var fli in loadInfo)
{ {
object val; 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<string, MiniYaml> md, out object ret)
{ {
ret = null; 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; 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; 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<T>(MiniYaml y) where T : new() public static T Load<T>(MiniYaml y) where T : new()
@@ -76,27 +82,31 @@ namespace OpenRA
} }
static readonly object[] NoIndexes = { }; 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 != null)
{ {
if (!field.HasAttribute<FieldFromYamlKeyAttribute>()) var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
field.SetValue(self, GetValue(field.Name, field.FieldType, value, field)); if (!sa.FromYamlKey)
field.SetValue(target, GetValue(field.Name, field.FieldType, value, field));
return; return;
} }
var prop = self.GetType().GetProperty(key.Trim()); var prop = target.GetType().GetProperty(key, flags);
if (prop != null) if (prop != null)
{ {
if (!prop.HasAttribute<FieldFromYamlKeyAttribute>()) var sa = prop.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
prop.SetValue(self, GetValue(prop.Name, prop.PropertyType, value, prop), NoIndexes); if (!sa.FromYamlKey)
prop.SetValue(target, GetValue(prop.Name, prop.PropertyType, value, prop), NoIndexes);
return; return;
} }
UnknownFieldAction(key.Trim(), self.GetType()); UnknownFieldAction(key, target.GetType());
} }
public static T GetValue<T>(string field, string value) public static T GetValue<T>(string field, string value)
@@ -378,49 +388,95 @@ namespace OpenRA
return InvalidValueAction(p, fieldType, field); return InvalidValueAction(p, fieldType, field);
} }
static Cache<Type, Dictionary<FieldInfo, Func<string, Type, MiniYaml, object>>> typeLoadInfo = new Cache<Type, Dictionary<FieldInfo, Func<string, Type, MiniYaml, object>>>(GetTypeLoadInfo); public sealed class FieldLoadInfo
static Dictionary<FieldInfo, Func<string, Type, MiniYaml, object>> GetTypeLoadInfo(Type type)
{ {
var ret = new Dictionary<FieldInfo, Func<string, Type, MiniYaml, object>>(); public readonly FieldInfo Field;
public readonly SerializeAttribute Attribute;
public readonly string YamlName;
public readonly Func<MiniYaml, object> Loader;
foreach (var ff in type.GetFields()) internal FieldLoadInfo(FieldInfo field, SerializeAttribute attr, string yamlName, Func<MiniYaml, object> loader = null)
{
Field = field;
Attribute = attr;
YamlName = yamlName;
Loader = loader;
}
}
public static IEnumerable<FieldLoadInfo> GetTypeLoadInfo(Type type, bool includePrivateByDefault = false)
{
return typeLoadInfo[type].Where(fli => includePrivateByDefault || fli.Field.IsPublic || (fli.Attribute.Serialize && !fli.Attribute.IsDefault));
}
static Cache<Type, List<FieldLoadInfo>> typeLoadInfo = new Cache<Type, List<FieldLoadInfo>>(BuildTypeLoadInfo);
static List<FieldLoadInfo> BuildTypeLoadInfo(Type type)
{
var ret = new List<FieldLoadInfo>();
foreach (var ff in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{ {
var field = ff; var field = ff;
var ignore = field.GetCustomAttributes<IgnoreAttribute>(false);
var loadUsing = field.GetCustomAttributes<LoadUsingAttribute>(false); var sa = field.GetCustomAttributes<SerializeAttribute>(false).DefaultIfEmpty(SerializeAttribute.Default).First();
var fromYamlKey = field.GetCustomAttributes<FieldFromYamlKeyAttribute>(false); if (!sa.Serialize)
if (loadUsing.Length != 0) continue;
ret[field] = (_1, fieldType, yaml) => loadUsing[0].LoaderFunc(field)(yaml);
else if (fromYamlKey.Length != 0) var yamlName = string.IsNullOrEmpty(sa.YamlName) ? field.Name : sa.YamlName;
ret[field] = (f, ft, yaml) => GetValue(f, ft, yaml.Value, field);
else if (ignore.Length == 0) var loader = sa.GetLoader(type);
ret[field] = null; 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; return ret;
} }
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
public sealed class IgnoreAttribute : Attribute { } public sealed class IgnoreAttribute : SerializeAttribute
{
public IgnoreAttribute()
: base(false) { }
}
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
public sealed class LoadUsingAttribute : Attribute public sealed class LoadUsingAttribute : SerializeAttribute
{ {
Func<MiniYaml, object> loaderFuncCache;
public readonly string Loader;
public LoadUsingAttribute(string loader) public LoadUsingAttribute(string loader)
{ {
Loader = loader; Loader = loader;
} }
}
internal Func<MiniYaml, object> 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; Serialize = serialize;
if (loaderFuncCache == null) }
loaderFuncCache = (Func<MiniYaml, object>)Delegate.CreateDelegate(typeof(Func<MiniYaml, object>), field.DeclaringType.GetMethod(Loader, BindingFlag));
return loaderFuncCache; internal Func<MiniYaml, object> GetLoader(Type type)
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
if (!string.IsNullOrEmpty(Loader))
return (Func<MiniYaml, object>)Delegate.CreateDelegate(typeof(Func<MiniYaml, object>), type.GetMethod(Loader, flags));
return null;
} }
} }
@@ -442,7 +498,14 @@ namespace OpenRA
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class TranslateAttribute : Attribute { } 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. // mirrors DescriptionAttribute from System.ComponentModel but we dont want to have to use that everywhere.
public sealed class DescAttribute : Attribute public sealed class DescAttribute : Attribute

View File

@@ -19,31 +19,34 @@ namespace OpenRA
{ {
public static class FieldSaver public static class FieldSaver
{ {
public static MiniYaml Save(object o) public static MiniYaml Save(object o, bool includePrivateByDefault = false)
{ {
var nodes = new List<MiniYamlNode>(); var nodes = new List<MiniYamlNode>();
string root = null; 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<FieldFromYamlKeyAttribute>()) if (info.Attribute.FromYamlKey)
root = FormatValue(o, f); root = FormatValue(o, info.Field);
else 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); 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()) if (o.GetType() != from.GetType())
throw new InvalidOperationException("FieldLoader: can't diff objects of different types"); throw new InvalidOperationException("FieldLoader: can't diff objects of different types");
var fields = o.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance) var fields = FieldLoader.GetTypeLoadInfo(o.GetType(), includePrivateByDefault)
.Where(f => FormatValue(o, f) != FormatValue(from, f)); .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) public static MiniYamlNode SaveField(object o, string field)

View File

@@ -320,18 +320,18 @@ namespace OpenRA.Utility
foreach (var line in traitDescLines) foreach (var line in traitDescLines)
doc.AppendLine(line); doc.AppendLine(line);
var fields = t.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); var infos = FieldLoader.GetTypeLoadInfo(t);
if (!fields.Any()) if (!infos.Any())
continue; continue;
doc.AppendLine("<table>"); doc.AppendLine("<table>");
doc.AppendLine("<tr><th>Property</th><th>Default Value</th><th>Type</th><th>Description</th></tr>"); doc.AppendLine("<tr><th>Property</th><th>Default Value</th><th>Type</th><th>Description</th></tr>");
var liveTraitInfo = Game.modData.ObjectCreator.CreateBasic(t); var liveTraitInfo = Game.modData.ObjectCreator.CreateBasic(t);
foreach (var f in fields) foreach (var info in infos)
{ {
var fieldDescLines = f.GetCustomAttributes<DescAttribute>(true).SelectMany(d => d.Lines); var fieldDescLines = info.Field.GetCustomAttributes<DescAttribute>(true).SelectMany(d => d.Lines);
var fieldType = FriendlyTypeName(f.FieldType); var fieldType = FriendlyTypeName(info.Field.FieldType);
var defaultValue = FieldSaver.SaveField(liveTraitInfo, f.Name).Value.Value; var defaultValue = FieldSaver.SaveField(liveTraitInfo, info.Field.Name).Value.Value;
doc.Append("<tr><td>{0}</td><td>{1}</td><td>{2}</td>".F(f.Name, defaultValue, fieldType)); doc.Append("<tr><td>{0}</td><td>{1}</td><td>{2}</td>".F(info.YamlName, defaultValue, fieldType));
doc.Append("<td>"); doc.Append("<td>");
foreach (var line in fieldDescLines) foreach (var line in fieldDescLines)
doc.Append(line + " "); doc.Append(line + " ");