CrabUI
Loading...
Searching...
No Matches
CUIComponent.Serialization.cs
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Reflection;
5using System.Diagnostics;
6using System.Runtime.CompilerServices;
7using System.IO;
8
9using Barotrauma;
10using Microsoft.Xna.Framework;
11using Microsoft.Xna.Framework.Input;
12using Microsoft.Xna.Framework.Graphics;
13
14using System.Xml;
15using System.Xml.Linq;
16using HarmonyLib;
17
18namespace CrabUI
19{
20 public partial class CUIComponent
21 {
22 public record CompareResult(bool equal, string firstMismatch = "")
23 {
24 public static implicit operator bool(CompareResult r) => r.equal;
25 }
26
27 public static bool DeepCompareVerbose(CUIComponent a, CUIComponent b)
28 {
29 CompareResult result = DeepCompare(a, b);
30 if (result.equal) CUI.Log($"{a} == {b}");
31 else CUI.Log($"{result.firstMismatch}");
32 return result.equal;
33 }
34 public static CompareResult DeepCompare(CUIComponent a, CUIComponent b)
35 {
36 if (a.GetType() != b.GetType()) return new CompareResult(false, $"type mismatch: {a} | {b}");
37
38 Type T = a.GetType();
39 CUITypeMetaData meta = CUITypeMetaData.Get(T);
40
41 foreach (var (key, pi) in meta.Serializable)
42 {
43 if (!object.Equals(pi.GetValue(a), pi.GetValue(b)))
44 {
45 return new CompareResult(false, $"{pi}: {a}{pi.GetValue(a)} | {b}{pi.GetValue(b)}");
46 }
47 }
48
49 if (a.Children.Count != b.Children.Count)
50 {
51 return new CompareResult(false, $"child count mismatch: {a}{CUI.ArrayToString(a.Children)} | {b}{CUI.ArrayToString(b.Children)}");
52 }
53
54 for (int i = 0; i < a.Children.Count; i++)
55 {
56 CompareResult sub = DeepCompare(a.Children[i], b.Children[i]);
57 if (!sub.equal) return sub;
58 }
59
60 return new CompareResult(true);
61 }
62
63 #region State --------------------------------------------------------
64
65 /// <summary>
66 /// State is just a clone component with copies of all props
67 /// </summary>
68 public Dictionary<string, CUIComponent> States { get; set; } = new();
69 // TODO why all clones are unreal? this is sneaky, and i don't remember what's it for
70 public CUIComponent Clone()
71 {
72 CUIComponent clone = new CUIComponent()
73 {
74 Unreal = true,
75 };
76 clone.ApplyState(this);
77 return clone;
78 }
79
80 public void SaveStateAs(string name) => States[name] = this.Clone();
81 public void LoadState(string name) => ApplyState(States.GetValueOrDefault(name));
82 public void ForgetState(string name) => States.Remove(name);
83
84 //TODO think about edge cases (PassPropsToChild)
85 public void ApplyState(CUIComponent state)
86 {
87 Stopwatch sw = Stopwatch.StartNew();
88 if (state == null) return;
89
90 //TODO why not closest relative?
91 Type targetType = state.GetType() == GetType() ? GetType() : typeof(CUIComponent);
92
93 CUITypeMetaData meta = CUITypeMetaData.Get(targetType);
94
95 //TODO Megacringe, fix it
96 foreach (PropertyInfo pi in meta.Serializable.Values)
97 {
98 if (pi.PropertyType.IsValueType || pi.PropertyType == typeof(string))
99 {
100 pi.SetValue(this, pi.GetValue(state));
101 }
102 else
103 {
104 object value = pi.GetValue(state);
105 if (value == null)
106 {
107 pi.SetValue(this, null);
108 continue;
109 }
110
111 if (pi.PropertyType.IsAssignableTo(typeof(ICloneable)))
112 {
113 ICloneable cloneable = (ICloneable)pi.GetValue(state);
114 object clone = cloneable.Clone();
115 pi.SetValue(this, clone);
116 }
117 else
118 {
119 CUI.Info($"Ekhem, can't copy {pi} prop from {state} to {this} because it's not cloneable");
120 }
121 }
122 }
123
124 //TODO Megacringe, fix it
125 foreach (PropertyInfo pi in meta.Serializable.Values)
126 {
127 if (pi.PropertyType.IsValueType && !object.Equals(pi.GetValue(state), pi.GetValue(this)))
128 {
129 pi.SetValue(this, pi.GetValue(state));
130 }
131 }
132
133 }
134
135 #endregion
136 #region XML --------------------------------------------------------
137
138 public string SavePath { get; set; }
139
140 public virtual XElement ToXML(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
141 {
142 try
143 {
144 if (!Serializable) return null;
145
146 Type type = GetType();
147
148 XElement e = new XElement(type.Name);
149
150 PackProps(e, propAttribute);
151
152 foreach (CUIComponent child in Children)
153 {
154 if (this.SerializeChildren)
155 {
156 e.Add(child.ToXML(propAttribute));
157 }
158 }
159
160 return e;
161 }
162 catch (Exception e)
163 {
164 CUI.Warning(e);
165 return new XElement("Error", e.Message);
166 }
167 }
168
169
170 public virtual void FromXML(XElement element, string baseFolder = null)
171 {
172 //RemoveAllChildren();
173
174 foreach (XElement childElement in element.Elements())
175 {
176 Type childType = CUIReflection.GetComponentTypeByName(childElement.Name.ToString());
177 if (childType == null) continue;
178
179 CUIComponent child = (CUIComponent)Activator.CreateInstance(childType);
180 child.FromXML(childElement, baseFolder);
181
182 if (child.MergeSerialization)
183 {
184 try
185 {
186 if (child.AKA == null || !this.NamedComponents.ContainsKey(child.AKA))
187 {
188 CUI.Warning($"Can't merge {child} into {this}");
189 CUI.Warning($"Merge deserialization requre matching AKA");
190 this.Append(child, child.AKA);
191 }
192 else
193 {
194 List<CUIComponent> children = new List<CUIComponent>(child.Children);
195 foreach (CUIComponent c in children)
196 {
197 this[child.AKA].Append(c);
198 }
199 }
200 }
201 catch (Exception e)
202 {
203 CUI.Warning($"Merge deserialization of {child} into {this}[{child.AKA}] failed");
204 CUI.Warning(e.Message);
205 }
206 }
207 else
208 {
209 if (child.AKA != null && this.NamedComponents.ContainsKey(child.AKA))
210 {
211 if (child.ReplaceSerialization)
212 {
213 this.RemoveChild(this[child.AKA]);
214 }
215 else
216 {
217 CUI.Warning($"{this} already contains {child.AKA}, so deserialization will create a duplicate\nTry using ReplaceSerialization or MergeSerialization");
218 }
219 }
220
221 this.Append(child, child.AKA);
222 }
223
224 //CUI.Log($"{this}[{child.AKA}] = {child} ");
225 }
226
227 ExtractProps(element, baseFolder);
228 }
229
230 protected void ExtractProps(XElement element, string baseFolder = null)
231 {
232 Type type = GetType();
233
234 CUITypeMetaData meta = CUITypeMetaData.Get(type);
235
236 foreach (XAttribute attribute in element.Attributes())
237 {
238 if (!meta.Serializable.ContainsKey(attribute.Name.ToString()))
239 {
240 CUI.Warning($"Can't parse prop {attribute.Name} in {type.Name} because type metadata doesn't contain that prop (is it a property? fields aren't supported yet)");
241 continue;
242 }
243
244 PropertyInfo prop = meta.Serializable[attribute.Name.ToString()];
245
246 try
247 {
248 if (prop.PropertyType == typeof(CUISprite) && baseFolder != null)
249 {
250 prop.SetValue(this, CUISprite.ParseWithContext(attribute.Value, baseFolder));
251 }
252 else
253 {
254 prop.SetValue(this, CUIParser.Parse(attribute.Value, prop.PropertyType, false));
255 }
256 }
257 catch (Exception e)
258 {
259 CUI.Warning($"Can't parse {attribute.Value} into {prop.PropertyType.Name}\n{e}");
260 }
261 }
262 }
263
264
265 protected void PackProps(XElement element, CUIAttribute propAttribute = CUIAttribute.CUISerializable)
266 {
267 Type type = GetType();
268 CUITypeMetaData meta = CUITypeMetaData.Get(type);
269
270 SortedDictionary<string, PropertyInfo> props = propAttribute switch
271 {
272 CUIAttribute.CUISerializable => meta.Serializable,
273 CUIAttribute.Calculated => meta.Calculated,
274 _ => meta.Serializable,
275 };
276
277 foreach (string key in props.Keys)
278 {
279 try
280 {
281 object value = props[key].GetValue(this);
282 // it's default value for this prop
283 if (!ForceSaveAllProps && meta.Default != null && Object.Equals(value, CUIReflection.GetNestedValue(meta.Default, key)))
284 {
285 continue;
286 }
287
288 element?.SetAttributeValue(key, CUIParser.Serialize(value, false));
289 }
290 catch (Exception e)
291 {
292 CUI.Warning($"Failed to serialize prop: {e.Message}");
293 CUI.Warning($"{key} in {this}");
294 }
295 }
296 }
297
298
299 public string Serialize(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
300 {
301 try
302 {
303 XElement e = this.ToXML(propAttribute);
304 return e.ToString();
305 }
306 catch (Exception e)
307 {
308 CUI.Error(e);
309 return e.Message;
310 }
311 }
312 public static CUIComponent Deserialize(string raw, string baseFolder = null)
313 {
314 return Deserialize(XElement.Parse(raw));
315 }
316
317 public static CUIComponent Deserialize(XElement e, string baseFolder = null)
318 {
319 try
320 {
321 Type type = CUIReflection.GetComponentTypeByName(e.Name.ToString());
322 if (type == null) return null;
323
324 CUIComponent c = (CUIComponent)Activator.CreateInstance(type);
325 // c.RemoveAllChildren();
326 c.FromXML(e, baseFolder);
327 CUIComponent.RunRecursiveOn(c, (component) => component.Hydrate());
328
329 return c;
330 }
331 catch (Exception ex)
332 {
333 CUIDebug.Error(ex);
334 return null;
335 }
336 }
337
338 public void LoadSelfFromFile(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false)
339 {
340 try
341 {
342 XDocument xdoc = XDocument.Load(path);
343
344 RemoveAllChildren();
345 if (searchForSpritesInTheSameFolder) FromXML(xdoc.Root, Path.GetDirectoryName(path));
346 else FromXML(xdoc.Root);
347
348 CUIComponent.RunRecursiveOn(this, (component) => component.Hydrate());
349 SavePath = path;
350
351 if (saveAfterLoad) SaveToTheSamePath();
352 }
353 catch (Exception ex)
354 {
355 CUI.Warning(ex);
356 }
357 }
358
359 public static CUIComponent LoadFromFile(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false)
360 {
361 try
362 {
363 XDocument xdoc = XDocument.Load(path);
364 CUIComponent result;
365 if (searchForSpritesInTheSameFolder)
366 {
367 result = Deserialize(xdoc.Root, Path.GetDirectoryName(path));
368 }
369 else result = Deserialize(xdoc.Root);
370
371 result.SavePath = path;
372
373 if (saveAfterLoad) result.SaveToTheSamePath();
374
375 return result;
376 }
377 catch (Exception ex)
378 {
379 CUIDebug.Error(ex);
380 return null;
381 }
382 }
383
384 public static T LoadFromFile<T>(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false) where T : CUIComponent
385 {
386 try
387 {
388 XDocument xdoc = XDocument.Load(path);
389 T result;
390 if (searchForSpritesInTheSameFolder)
391 {
392 result = (T)Deserialize(xdoc.Root, Path.GetDirectoryName(path));
393 }
394 else result = (T)Deserialize(xdoc.Root);
395
396 result.SavePath = path;
397
398 if (saveAfterLoad) result.SaveToTheSamePath();
399
400 return result;
401 }
402 catch (Exception ex)
403 {
404 CUIDebug.Error(ex);
405 return null;
406 }
407 }
408
409 public void LoadFromTheSameFile()
410 {
411 if (SavePath == null)
412 {
413 CUI.Warning($"Can't load {this} from The Same Path, SavePath is null");
414 return;
415 }
416 LoadSelfFromFile(SavePath);
417 }
418
419 public void SaveToTheSamePath()
420 {
421 if (SavePath == null)
422 {
423 CUI.Warning($"Can't save {this} To The Same Path, SavePath is null");
424 return;
425 }
426 SaveToFile(SavePath);
427 }
428
429 public void SaveToFile(string path, CUIAttribute propAttribute = CUIAttribute.CUISerializable)
430 {
431 try
432 {
433 XDocument xdoc = new XDocument();
434 xdoc.Add(this.ToXML(propAttribute));
435 xdoc.Save(path);
436 SavePath = path;
437 }
438 catch (Exception e)
439 {
440 CUI.Warning(e);
441 }
442 }
443
444 /// <summary>
445 /// Experimental method
446 /// Here you can add data/ callbacks/ save stuff to variables
447 /// after loading a xml skeletom
448 /// </summary>
449 public virtual void Hydrate()
450 {
451
452 }
453
454 #endregion
455 }
456}
Base class for all components.
virtual void Hydrate()
Experimental method Here you can add data/ callbacks/ save stuff to variables after loading a xml...
Dictionary< string, CUIComponent > States
State is just a clone component with copies of all props.