Add quick interactions locally
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.IO;
|
||||
|
||||
using Barotrauma;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace QICrabUI
|
||||
{
|
||||
public partial class CUIComponent
|
||||
{
|
||||
public record CompareResult(bool equal, string firstMismatch = "")
|
||||
{
|
||||
public static implicit operator bool(CompareResult r) => r.equal;
|
||||
}
|
||||
|
||||
public static bool DeepCompareVerbose(CUIComponent a, CUIComponent b)
|
||||
{
|
||||
CompareResult result = DeepCompare(a, b);
|
||||
if (result.equal) CUI.Log($"{a} == {b}");
|
||||
else CUI.Log($"{result.firstMismatch}");
|
||||
return result.equal;
|
||||
}
|
||||
public static CompareResult DeepCompare(CUIComponent a, CUIComponent b)
|
||||
{
|
||||
if (a.GetType() != b.GetType()) return new CompareResult(false, $"type mismatch: {a} | {b}");
|
||||
|
||||
Type T = a.GetType();
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(T);
|
||||
|
||||
foreach (var (key, pi) in meta.Serializable)
|
||||
{
|
||||
if (!object.Equals(pi.GetValue(a), pi.GetValue(b)))
|
||||
{
|
||||
return new CompareResult(false, $"{pi}: {a}{pi.GetValue(a)} | {b}{pi.GetValue(b)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (a.Children.Count != b.Children.Count)
|
||||
{
|
||||
return new CompareResult(false, $"child count mismatch: {a}{CUI.ArrayToString(a.Children)} | {b}{CUI.ArrayToString(b.Children)}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < a.Children.Count; i++)
|
||||
{
|
||||
CompareResult sub = DeepCompare(a.Children[i], b.Children[i]);
|
||||
if (!sub.equal) return sub;
|
||||
}
|
||||
|
||||
return new CompareResult(true);
|
||||
}
|
||||
|
||||
#region State --------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// State is just a clone component with copies of all props
|
||||
/// </summary>
|
||||
public Dictionary<string, CUIComponent> States { get; set; } = new();
|
||||
// TODO why all clones are unreal? this is sneaky, and i don't remember what's it for
|
||||
public CUIComponent Clone()
|
||||
{
|
||||
CUIComponent clone = new CUIComponent()
|
||||
{
|
||||
Unreal = true,
|
||||
};
|
||||
clone.ApplyState(this);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public void SaveStateAs(string name) => States[name] = this.Clone();
|
||||
public void LoadState(string name) => ApplyState(States.GetValueOrDefault(name));
|
||||
public void ForgetState(string name) => States.Remove(name);
|
||||
|
||||
//TODO think about edge cases (PassPropsToChild)
|
||||
public void ApplyState(CUIComponent state)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
if (state == null) return;
|
||||
|
||||
//TODO why not closest relative?
|
||||
Type targetType = state.GetType() == GetType() ? GetType() : typeof(CUIComponent);
|
||||
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(targetType);
|
||||
|
||||
//TODO Megacringe, fix it
|
||||
foreach (PropertyInfo pi in meta.Serializable.Values)
|
||||
{
|
||||
if (pi.PropertyType.IsValueType || pi.PropertyType == typeof(string))
|
||||
{
|
||||
pi.SetValue(this, pi.GetValue(state));
|
||||
}
|
||||
else
|
||||
{
|
||||
object value = pi.GetValue(state);
|
||||
if (value == null)
|
||||
{
|
||||
pi.SetValue(this, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pi.PropertyType.IsAssignableTo(typeof(ICloneable)))
|
||||
{
|
||||
ICloneable cloneable = (ICloneable)pi.GetValue(state);
|
||||
object clone = cloneable.Clone();
|
||||
pi.SetValue(this, clone);
|
||||
}
|
||||
else
|
||||
{
|
||||
CUI.Info($"Ekhem, can't copy {pi} prop from {state} to {this} because it's not cloneable");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO Megacringe, fix it
|
||||
foreach (PropertyInfo pi in meta.Serializable.Values)
|
||||
{
|
||||
if (pi.PropertyType.IsValueType && !object.Equals(pi.GetValue(state), pi.GetValue(this)))
|
||||
{
|
||||
pi.SetValue(this, pi.GetValue(state));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region XML --------------------------------------------------------
|
||||
|
||||
public static bool ForceSaveAllProps { get; set; } = false;
|
||||
public static bool SaveAfterLoad { get; set; } = true;
|
||||
|
||||
public string SavePath { get; set; }
|
||||
|
||||
public virtual XElement ToXML(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Unserializable) return null;
|
||||
|
||||
Type type = GetType();
|
||||
|
||||
XElement e = new XElement(type.Name);
|
||||
|
||||
PackProps(e, propAttribute);
|
||||
|
||||
foreach (CUIComponent child in Children)
|
||||
{
|
||||
if (!this.BreakSerialization)
|
||||
{
|
||||
e.Add(child.ToXML(propAttribute));
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning(e);
|
||||
return new XElement("Error", e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual void FromXML(XElement element, string baseFolder = null)
|
||||
{
|
||||
foreach (XElement childElement in element.Elements())
|
||||
{
|
||||
Type childType = CUIReflection.GetComponentTypeByName(childElement.Name.ToString());
|
||||
if (childType == null) continue;
|
||||
|
||||
CUIComponent child = (CUIComponent)Activator.CreateInstance(childType);
|
||||
child.FromXML(childElement, baseFolder);
|
||||
|
||||
//CUI.Log($"{this}[{child.AKA}] = {child} ");
|
||||
this.Append(child, child.AKA);
|
||||
}
|
||||
|
||||
ExtractProps(element, baseFolder);
|
||||
}
|
||||
|
||||
protected void ExtractProps(XElement element, string baseFolder = null)
|
||||
{
|
||||
Type type = GetType();
|
||||
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(type);
|
||||
|
||||
foreach (XAttribute attribute in element.Attributes())
|
||||
{
|
||||
if (!meta.Serializable.ContainsKey(attribute.Name.ToString()))
|
||||
{
|
||||
CUIDebug.Error($"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)");
|
||||
continue;
|
||||
}
|
||||
|
||||
PropertyInfo prop = meta.Serializable[attribute.Name.ToString()];
|
||||
|
||||
MethodInfo parse = null;
|
||||
if (CUIExtensions.Parse.ContainsKey(prop.PropertyType))
|
||||
{
|
||||
parse = CUIExtensions.Parse[prop.PropertyType];
|
||||
}
|
||||
|
||||
parse ??= prop.PropertyType.GetMethod(
|
||||
"Parse",
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
new Type[] { typeof(string) }
|
||||
);
|
||||
|
||||
|
||||
Func<string, object> ParseWithContext = null;
|
||||
//HACK
|
||||
if (prop.PropertyType == typeof(CUISprite) && baseFolder != null)
|
||||
{
|
||||
ParseWithContext = (raw) => CUISprite.ParseWithContext(raw, baseFolder);
|
||||
}
|
||||
|
||||
|
||||
if (parse == null)
|
||||
{
|
||||
if (prop.PropertyType.IsEnum)
|
||||
{
|
||||
try
|
||||
{
|
||||
prop.SetValue(this, Enum.Parse(prop.PropertyType, attribute.Value));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUIDebug.Error($"Can't parse {attribute.Value} into {prop.PropertyType.Name}\n{e}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CUIDebug.Error($"Can't parse prop {prop.Name} in {type.Name} because it's type {prop.PropertyType.Name} is missing Parse method");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
object result = null;
|
||||
if (ParseWithContext != null)
|
||||
{
|
||||
result = ParseWithContext(attribute.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = parse.Invoke(null, new object[] { attribute.Value });
|
||||
}
|
||||
prop.SetValue(this, result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUIDebug.Error($"Can't parse {attribute.Value} into {prop.PropertyType.Name}\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void PackProps(XElement element, CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||
{
|
||||
Type type = GetType();
|
||||
CUITypeMetaData meta = CUITypeMetaData.Get(type);
|
||||
|
||||
SortedDictionary<string, PropertyInfo> props = propAttribute switch
|
||||
{
|
||||
CUIAttribute.CUISerializable => meta.Serializable,
|
||||
CUIAttribute.Calculated => meta.Calculated,
|
||||
_ => meta.Serializable,
|
||||
};
|
||||
|
||||
foreach (string key in props.Keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
object value = props[key].GetValue(this);
|
||||
// it's default value for this prop
|
||||
if (!ForceSaveAllProps && meta.Default != null && Object.Equals(value, CUIReflection.GetNestedValue(meta.Default, key)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
MethodInfo customToString = CUIExtensions.CustomToString.GetValueOrDefault(props[key].PropertyType);
|
||||
|
||||
if (customToString != null)
|
||||
{
|
||||
element?.SetAttributeValue(key, customToString.Invoke(null, new object[] { value }));
|
||||
}
|
||||
else
|
||||
{
|
||||
element?.SetAttributeValue(key, value);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning($"Failed to serialize prop: {e.Message}");
|
||||
CUI.Warning($"{key} in {this}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string Serialize(CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||
{
|
||||
try
|
||||
{
|
||||
XElement e = this.ToXML(propAttribute);
|
||||
return e.ToString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Error(e);
|
||||
return e.Message;
|
||||
}
|
||||
}
|
||||
public static CUIComponent Deserialize(string raw, string baseFolder = null)
|
||||
{
|
||||
return Deserialize(XElement.Parse(raw));
|
||||
}
|
||||
|
||||
public static CUIComponent Deserialize(XElement e, string baseFolder = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Type type = CUIReflection.GetComponentTypeByName(e.Name.ToString());
|
||||
if (type == null) return null;
|
||||
|
||||
CUIComponent c = (CUIComponent)Activator.CreateInstance(type);
|
||||
// c.RemoveAllChildren();
|
||||
c.FromXML(e, baseFolder);
|
||||
CUIComponent.RunRecursiveOn(c, (component) => component.Hydrate());
|
||||
|
||||
return c;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CUIDebug.Error(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadSelfFromFile(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument xdoc = XDocument.Load(path);
|
||||
|
||||
RemoveAllChildren();
|
||||
if (searchForSpritesInTheSameFolder) FromXML(xdoc.Root, Path.GetDirectoryName(path));
|
||||
else FromXML(xdoc.Root);
|
||||
|
||||
CUIComponent.RunRecursiveOn(this, (component) => component.Hydrate());
|
||||
SavePath = path;
|
||||
|
||||
if (SaveAfterLoad && saveAfterLoad) SaveToTheSamePath();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CUI.Warning(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static CUIComponent LoadFromFile(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument xdoc = XDocument.Load(path);
|
||||
CUIComponent result;
|
||||
if (searchForSpritesInTheSameFolder)
|
||||
{
|
||||
result = Deserialize(xdoc.Root, Path.GetDirectoryName(path));
|
||||
}
|
||||
else result = Deserialize(xdoc.Root);
|
||||
|
||||
result.SavePath = path;
|
||||
|
||||
if (SaveAfterLoad && saveAfterLoad) result.SaveToTheSamePath();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CUIDebug.Error(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static T LoadFromFile<T>(string path, bool searchForSpritesInTheSameFolder = true, bool saveAfterLoad = false) where T : CUIComponent
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument xdoc = XDocument.Load(path);
|
||||
T result;
|
||||
if (searchForSpritesInTheSameFolder)
|
||||
{
|
||||
result = (T)Deserialize(xdoc.Root, Path.GetDirectoryName(path));
|
||||
}
|
||||
else result = (T)Deserialize(xdoc.Root);
|
||||
|
||||
result.SavePath = path;
|
||||
|
||||
if (SaveAfterLoad && saveAfterLoad) result.SaveToTheSamePath();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CUIDebug.Error(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromTheSameFile()
|
||||
{
|
||||
if (SavePath == null)
|
||||
{
|
||||
CUI.Warning($"Can't load {this} from The Same Path, SavePath is null");
|
||||
return;
|
||||
}
|
||||
LoadSelfFromFile(SavePath);
|
||||
}
|
||||
|
||||
public void SaveToTheSamePath()
|
||||
{
|
||||
if (SavePath == null)
|
||||
{
|
||||
CUI.Warning($"Can't save {this} To The Same Path, SavePath is null");
|
||||
return;
|
||||
}
|
||||
SaveToFile(SavePath);
|
||||
}
|
||||
|
||||
public void SaveToFile(string path, CUIAttribute propAttribute = CUIAttribute.CUISerializable)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument xdoc = new XDocument();
|
||||
xdoc.Add(this.ToXML(propAttribute));
|
||||
xdoc.Save(path);
|
||||
SavePath = path;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CUI.Warning(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Experimental method
|
||||
/// Here you can add data/ callbacks/ save stuff to variables
|
||||
/// after loading a xml skeletom
|
||||
/// </summary>
|
||||
public virtual void Hydrate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user