Skip to content

Commit

Permalink
Refactor List/Hashset as well to have generic Collection helpers to l…
Browse files Browse the repository at this point in the history
…oad/save this data. Refactor helpers to have shared base dict of types and to use hashcodes explicitly for perf.
  • Loading branch information
NathanKell committed Nov 23, 2023
1 parent 0a1093a commit 02d91c0
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 192 deletions.
194 changes: 194 additions & 0 deletions Source/ROUtils/DataTypes/CollectionHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using KSPCommunityFixes.Modding;
using UnityEngine;

namespace ROUtils.DataTypes
{
public abstract class PersistenceHelper
{
protected static readonly Dictionary<int, Type> _TypeCache = new Dictionary<int, Type>();

protected const int VERSION = 3;
protected int _version; // will be set on load

/// <summary>
/// Returns a type from a type name and its hash (fuly qualified name preferred).
/// If the type can't be found or the baseType isn't assignable from the found type,
/// the base type is used. Successfully found types are cached.
/// </summary>
/// <param name="typeName"></param>
/// <param name="hash"></param>
/// <param name="baseType"></param>
/// <returns></returns>
public static Type GetTypeFromString(string typeName, int hash, Type baseType)
{
if (!_TypeCache.TryGetValue(hash, out var cachedType))
{
cachedType = HarmonyLib.AccessTools.TypeByName(typeName);
_TypeCache[hash] = cachedType;
}

if (cachedType != null && baseType.IsAssignableFrom(cachedType))
return cachedType;

return baseType;
}
}

public abstract class ICollectionPersistence<T> : PersistenceHelper, IConfigNode
{
protected static readonly Type _Type = typeof(T);

protected ICollection<T> _coll;

public ICollectionPersistence(ICollection<T> coll) { _coll = coll; }

public abstract void Load(ConfigNode node);

public abstract void Save(ConfigNode node);
}

public class ICollectionPersistenceNode<T> : ICollectionPersistence<T> where T : IConfigNode
{
protected static readonly string _TypeName = typeof(T).Name;
protected static readonly int _TypeHash = _TypeName.GetHashCode();
protected static readonly int _DefaultNodeNameHash = "ITEM".GetHashCode();

public ICollectionPersistenceNode(ICollection<T> coll) : base(coll) { }

public override void Load(ConfigNode node)
{
_coll.Clear();
int version = 1;
node.TryGetValue("version", ref version);

foreach (ConfigNode n in node.nodes)
{
T item;
int hash = n.name.GetHashCode();
if (version == 1 || hash == _DefaultNodeNameHash || hash == _TypeHash)
{
item = Activator.CreateInstance<T>();
}
else
{
item = (T)Activator.CreateInstance(GetTypeFromString(n.name, hash, _Type));
}
item.Load(n);
_coll.Add(item);
}
version = VERSION;
}

public override void Save(ConfigNode node)
{
node.AddValue("version", _version);
foreach (var item in _coll)
{
var type = item.GetType();
ConfigNode n = new ConfigNode(type == _Type ? _TypeName : type.FullName);
item.Save(n);
node.AddNode(n);
}
}
}

public class ICollectionPersistenceParseable<T> : ICollectionPersistence<T> where T : class
{
private enum ParseableType
{
INVALID,
ProtoCrewMember,
}

private static ParseableType GetParseableType()
{
if (_Type == typeof(ProtoCrewMember))
return ParseableType.ProtoCrewMember;

return ParseableType.INVALID;
}

private static readonly ParseableType _ParseType = GetParseableType();

protected static readonly string _TypeName = typeof(T).Name;
protected static readonly int _TypeHash = _TypeName.GetHashCode();
protected static readonly int _DefaultNodeNameHash = "ITEM".GetHashCode();

public ICollectionPersistenceParseable(ICollection<T> coll) : base(coll) { }

private T FromValue(ConfigNode.Value v)
{
switch (_ParseType)
{
case ParseableType.ProtoCrewMember:
return HighLogic.CurrentGame.CrewRoster[v.value] as T;
}

return null;
}

private ConfigNode.Value ToValue(T item)
{
switch (_ParseType)
{
case ParseableType.ProtoCrewMember:
return new ConfigNode.Value("item", item.ToString());
}

return null;
}

public override void Load(ConfigNode node)
{
_coll.Clear();
foreach (ConfigNode.Value v in node.values)
{
T item = FromValue(v);
if (item != null)
_coll.Add(item);
}
}

public override void Save(ConfigNode node)
{
foreach (var item in _coll)
{
var v = ToValue(item);
if (v != null)
node.values.Add(v);
}
}
}

/// <summary>
/// NOTE: This does not have constraints because string is supported
/// but string is not a valuetype
/// </summary>
public class ICollectionPersistenceValueType<T> : ICollectionPersistence<T>
{
protected static readonly DataType _DataType = FieldData.ValueDataType(_Type);

public ICollectionPersistenceValueType(ICollection<T> coll) : base(coll) { }

public override void Load(ConfigNode node)
{
_coll.Clear();
foreach (ConfigNode.Value v in node.values)
{
T item = (T)FieldData.ReadValue(v.value, _DataType, _Type);
_coll.Add(item);
}
}

public override void Save(ConfigNode node)
{
foreach (var item in _coll)
{
string value = FieldData.WriteValue(item, _DataType);
node.AddValue("item", value);
}
}
}
}
50 changes: 19 additions & 31 deletions Source/ROUtils/DataTypes/DictionaryHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@

namespace ROUtils.DataTypes
{
public abstract class IDictionaryPersistence<TKey, TValue> : IConfigNode
public abstract class IDictionaryPersistence<TKey, TValue> : PersistenceHelper, IConfigNode
{
protected static readonly Type _KeyType = typeof(TKey);
protected static readonly Type _ValueType = typeof(TValue);
protected static readonly Dictionary<string, Type> _TypeCache = new Dictionary<string, Type>();

protected IDictionary<TKey, TValue> _dict;

protected const int VERSION = 3;
protected int version; // will be set on load

public IDictionaryPersistence(IDictionary<TKey, TValue> dict) { _dict = dict; }

public abstract void Load(ConfigNode node);
Expand All @@ -26,6 +22,8 @@ public abstract class IDictionaryPersistence<TKey, TValue> : IConfigNode
public abstract class IDictionaryPersistenceValueNode<TKey, TValue> : IDictionaryPersistence<TKey, TValue> where TValue: IConfigNode
{
protected static readonly string _ValueTypeName = typeof(TValue).Name;
protected static readonly int _ValueTypeHash = _ValueTypeName.GetHashCode();
protected static readonly int _DefaultValueNameHash = "VALUE".GetHashCode();

public IDictionaryPersistenceValueNode(IDictionary<TKey, TValue> dict) : base(dict) { }

Expand All @@ -36,20 +34,14 @@ protected virtual TValue GetValue(int i, ConfigNode valueNode)
{
var n = valueNode.nodes[i];
TValue value;
if (version == 1 || n.name == "VALUE" || n.name == _ValueTypeName)
int hash = n.name.GetHashCode();
if (_version == 1 || hash == _DefaultValueNameHash || hash == _ValueTypeHash)
{
value = Activator.CreateInstance<TValue>();
}
else
{
if (!_TypeCache.TryGetValue(n.name, out var type))
type = HarmonyLib.AccessTools.TypeByName(n.name);
if (type == null || !_ValueType.IsAssignableFrom(type))
type = _ValueType;
else
_TypeCache[n.name] = type;

value = (TValue)Activator.CreateInstance(type);
value = (TValue)Activator.CreateInstance(GetTypeFromString(n.name, hash, _ValueType));
}
value.Load(n);
return value;
Expand All @@ -68,8 +60,8 @@ public override void Load(ConfigNode node)
_dict.Clear();
ConfigNode keyNode = node.nodes[0];
ConfigNode valueNode = node.nodes[1];
version = 1;
node.TryGetValue("version", ref version);
_version = 1;
node.TryGetValue("version", ref _version);

int c = valueNode.nodes.Count;
for (int i = 0; i < c; ++i)
Expand All @@ -78,12 +70,12 @@ public override void Load(ConfigNode node)
TValue value = GetValue(i, valueNode);
_dict.Add(key, value);
}
version = VERSION;
_version = VERSION;
}

public override void Save(ConfigNode node)
{
node.AddValue("version", version);
node.AddValue("version", _version);
ConfigNode keyNode = node.AddNode("Keys");
ConfigNode valueNode = node.AddNode("Values");

Expand All @@ -98,27 +90,23 @@ public override void Save(ConfigNode node)
public class IDictionaryPersistenceBothObjects<TKey, TValue> : IDictionaryPersistenceValueNode<TKey, TValue> where TKey : IConfigNode where TValue : IConfigNode
{
protected static readonly string _KeyTypeName = typeof(TKey).Name;
protected static readonly int _KeyTypeHash = _KeyTypeName.GetHashCode();
protected static readonly int _DefaultKeyNameHash = "KEY".GetHashCode();

public IDictionaryPersistenceBothObjects(IDictionary<TKey, TValue> dict) : base(dict) { }

protected override TKey GetKey(int i, ConfigNode keyNode)
{
var n = keyNode.nodes[i];
TKey key;
if (version == 1 || n.name == "KEY" || n.name == _KeyTypeName)
int hash = n.name.GetHashCode();
if (_version == 1 || hash == _DefaultKeyNameHash || hash == _KeyTypeHash)
{
key = Activator.CreateInstance<TKey>();
}
else
{
if (!_TypeCache.TryGetValue(n.name, out var type))
type = HarmonyLib.AccessTools.TypeByName(n.name);
if (type == null || !_KeyType.IsAssignableFrom(type))
type = _KeyType;
else
_TypeCache[n.name] = type;

key = (TKey)Activator.CreateInstance(type);
key = (TKey)Activator.CreateInstance(GetTypeFromString(n.name, hash, _KeyType));
}
key.Load(n);
return key;
Expand Down Expand Up @@ -159,8 +147,8 @@ protected override void AddKey(TKey key, ConfigNode keyNode)
public override void Load(ConfigNode node)
{
_dict.Clear();
version = 1;
node.TryGetValue("version", ref version);
_version = 1;
node.TryGetValue("version", ref _version);
for (int i = 0; i < node.nodes.Count; ++i)
{
TKey key = GetKey(i, node);
Expand All @@ -173,12 +161,12 @@ public override void Load(ConfigNode node)
TValue value = GetValue(i, node);
_dict.Add(key, value);
}
version = VERSION;
_version = VERSION;
}

public override void Save(ConfigNode node)
{
node.AddValue("version", version);
node.AddValue("version", _version);
foreach (var kvp in _dict)
{
AddValue(kvp.Value, node);
Expand Down
Loading

0 comments on commit 02d91c0

Please sign in to comment.