Client LuaCsForBarotrauma
StructSerialization.cs
1 #nullable enable
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Immutable;
6 using System.Globalization;
7 using System.Linq;
8 using System.Reflection;
9 using System.Xml.Linq;
10 using System.Xml.Schema;
11 
12 namespace Barotrauma
13 {
14  public static class StructSerialization
15  {
16  private static readonly ImmutableDictionary<Type, MethodInfo> deserializeMethods;
17  private static readonly ImmutableDictionary<Type, MethodInfo> serializeMethods;
18 
19  public class SkipAttribute : Attribute { }
20 
21  private static bool ShouldSkip(this FieldInfo field)
22  => field.GetCustomAttribute<SkipAttribute>() != null;
23 
24  private static HandlerAttribute? ExtractHandler(this FieldInfo field)
25  => field.GetCustomAttribute<HandlerAttribute>();
26 
27  public class HandlerAttribute : Attribute
28  {
29  public readonly Func<string?, object?> Read;
30  public readonly Func<object?, string?> Write;
31 
32  public HandlerAttribute(Type handlerType)
33  {
34  var readAction =
35  handlerType.GetMethod(nameof(Read), BindingFlags.Public | BindingFlags.Static)
36  ?? throw new Exception($"Type {handlerType.Name} does not have a static {nameof(Read)} method");
37  var writeAction =
38  handlerType.GetMethod(nameof(Write), BindingFlags.Public | BindingFlags.Static)
39  ?? throw new Exception($"Type {handlerType.Name} does not have a static {nameof(Write)} method");
40  var paramArray = new object?[1];
41  Read = (s) =>
42  {
43  paramArray[0] = s;
44  return readAction.Invoke(null, paramArray);
45  };
46  Write = (o) =>
47  {
48  paramArray[0] = o;
49  return writeAction.Invoke(null, paramArray)?.ToString();
50  };
51  }
52  }
53 
54  static StructSerialization()
55  {
56  deserializeMethods =
57  typeof(StructSerialization)
58  .GetMethods(BindingFlags.Static | BindingFlags.Public)
59  .Where(m =>
60  {
61  if (!m.Name.StartsWith("Deserialize")) { return false; }
62  var parameters = m.GetParameters();
63  if (parameters.Length < 1 || parameters.Length > 2 ||
64  parameters[0].ParameterType != typeof(string))
65  {
66  return false;
67  }
68  return true;
69  })
70  .Select(m => (m.ReturnType, m))
71  .ToImmutableDictionary();
72 
73  serializeMethods =
74  typeof(StructSerialization)
75  .GetMethods(BindingFlags.Static | BindingFlags.Public)
76  .Where(m =>
77  {
78  if (!m.Name.StartsWith("Serialize")) { return false; }
79  var parameters = m.GetParameters();
80  if (parameters.Length != 1 ||
81  m.ReturnType != typeof(string))
82  {
83  return false;
84  }
85  return true;
86  })
87  .Select(m => (m.GetParameters()[0].ParameterType, m))
88  .ToImmutableDictionary();
89  }
90 
91  public static void CopyPropertiesFrom<T>(this ref T self, in T other) where T : struct
92  {
93  var fields = self.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => !f.IsInitOnly).ToArray();
94  foreach (var field in fields)
95  {
96  if (field.ShouldSkip()) { continue; }
97  field.SetValue(self, field.GetValue(other));
98  }
99  }
100 
101  public static void DeserializeElement<T>(this ref T self, XElement element) where T : struct
102  {
103  var fields = self.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance).ToArray();
104 
105  //box the struct here so we don't end up
106  //making a copy every time we feed this to reflection
107  object boxedSelf = self;
108  foreach (var field in fields)
109  {
110  if (field.ShouldSkip()) { continue; }
111  boxedSelf.TryDeserialize(field, element);
112  }
113  //copy the boxed struct into the original
114  self = (T)boxedSelf;
115  }
116 
117  private static void TryDeserialize(this object boxedSelf, FieldInfo field, XElement element)
118  {
119  string fieldName = field.Name.ToLowerInvariant();
120  string valueStr = element.GetAttributeString(fieldName, field.GetValue(boxedSelf)?.ToString() ?? "");
121 
122  var handler = field.ExtractHandler();
123 
124  if (handler != null)
125  {
126  field.SetValue(boxedSelf, handler.Read(valueStr));
127  }
128  else if (deserializeMethods.TryGetValue(field.FieldType, out MethodInfo? deserializeMethod))
129  {
130  object?[] parameters = { valueStr };
131  if (deserializeMethod.GetParameters().Length > 1)
132  {
133  Array.Resize(ref parameters, 2);
134  parameters[1] = field.GetValue(boxedSelf);
135  }
136  field.SetValue(boxedSelf, deserializeMethod.Invoke(boxedSelf, parameters));
137  }
138  else if (field.FieldType.IsEnum)
139  {
140  field.SetValue(boxedSelf, DeserializeEnum(field.FieldType, valueStr, (Enum)field.GetValue(boxedSelf)!));
141  }
142  }
143 
144  public static string DeserializeString(string str)
145  {
146  return str;
147  }
148 
149  public static bool DeserializeBool(string str, bool defaultValue)
150  {
151  if (bool.TryParse(str, out bool result)) { return result; }
152  return defaultValue;
153  }
154 
155  public static float DeserializeFloat(string str, float defaultValue)
156  {
157  if (float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out float result)) { return result; }
158  return defaultValue;
159  }
160 
161  public static Int32 DeserializeInt32(string str, Int32 defaultValue)
162  {
163  if (Int32.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out Int32 result)) { return result; }
164  return defaultValue;
165  }
166 
167  public static Identifier DeserializeIdentifier(string str)
168  {
169  return str.ToIdentifier();
170  }
171 
172  public static LanguageIdentifier DeserializeLanguageIdentifier(string str)
173  {
174  return str.ToLanguageIdentifier();
175  }
176 
177  public static Color DeserializeColor(string str)
178  {
179  return XMLExtensions.ParseColor(str);
180  }
181 
182  public static Enum DeserializeEnum(Type enumType, string str, Enum defaultValue)
183  {
184  if (Enum.TryParse(enumType, str, out object? result)) { return (Enum)result!; }
185  return defaultValue;
186  }
187 
188  public static void SerializeElement<T>(this ref T self, XElement element) where T : struct
189  {
190  var fields = self.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance).ToArray();
191 
192  foreach (var field in fields)
193  {
194  if (field.ShouldSkip()) { continue; }
195  self.TrySerialize(field, element);
196  }
197  }
198 
199  public static void TrySerialize<T>(this T self, FieldInfo field, XElement element) where T : struct
200  {
201  string fieldName = field.Name.ToLowerInvariant();
202  object? fieldValue = field.GetValue(self);
203 
204  string valueStr = fieldValue?.ToString() ?? "";
205 
206  var handler = field.ExtractHandler();
207 
208  if (handler != null)
209  {
210  valueStr = handler.Write(valueStr) ?? "";
211  }
212  else if (serializeMethods.TryGetValue(field.FieldType, out MethodInfo? method))
213  {
214  object?[] parameters = { fieldValue };
215  valueStr = (string)method.Invoke(self, parameters)!;
216  }
217 
218  element.SetAttributeValue(fieldName, valueStr);
219  }
220 
221  public static string SerializeBool(bool val)
222  => val ? "true" : "false";
223 
224  public static string SerializeInt32(Int32 val)
225  => val.ToString(CultureInfo.InvariantCulture);
226 
227  public static string SerializeFloat(float val)
228  => val.ToString(CultureInfo.InvariantCulture);
229 
230  public static string SerializeColor(Color val)
231  => val.ToStringHex();
232  }
233 }