Open tarik02 opened 8 years ago
There should probably be a way to easily check if two compounds have the same contents. I will add overrides of GetHashCode
and Equals
methods on NbtTag classes in next release. For now, you can use this implementation of IEqualityComparer<NbtTag>
:
/// <summary> Compares NbtTag for equality by comparing their types, names, and values. Considers compound tags to be equal
/// if they contain equal sets of tags. Considered list tags to be equal if their tags are equal and in the same order. </summary>
public class NbtComparer : IEqualityComparer<NbtTag> {
public static readonly NbtComparer Instance = new NbtComparer();
public bool Equals(NbtTag x, NbtTag y) {
if (x == y) return true;
if (x == null || y == null) return false;
return (x.TagType == y.TagType)
&& string.Equals(x.Name, y.Name) // null names are permitted
&& DeepEquals(x, y);
}
public int GetHashCode(NbtTag tag) {
var hash = tag.TagType.GetHashCode();
hash = hash*23 ^ (tag.Name ?? "").GetHashCode();
var rawValue = GetRawValue(tag);
if (rawValue != null) {
hash = hash*23 ^ rawValue.GetHashCode();
} else if (tag.TagType == NbtTagType.List) {
var list = (NbtList)tag;
hash = hash*23 ^ list.ListType.GetHashCode();
hash = hash*23 ^ list.Count.GetHashCode();
} else if (tag.TagType == NbtTagType.Compound) {
var comp = (NbtCompound)tag;
hash = hash*23 ^ comp.Count.GetHashCode();
} else {
// END and unknown
throw new ArgumentException("Cannot compare tags of type " + tag.TagType);
}
return hash;
}
// Compare detailed attributes of two given tags
private bool DeepEquals(NbtTag x, NbtTag y) {
// Assume that tags have same type and are non-null
var rawValue1 = GetRawValue(x);
if (rawValue1 != null) {
var rawValue2 = GetRawValue(y);
// Regular tags are equal if their values are equal
return Equals(rawValue1, rawValue2);
} else if (x.TagType == NbtTagType.Compound) {
var xComp = (NbtCompound)x;
var yComp = (NbtCompound)y;
// Compounds are equal if their child-count and contents are equal
return (xComp.Count == yComp.Count) &&
new HashSet<NbtTag>(xComp, this).SetEquals(yComp);
} else if (x.TagType == NbtTagType.List) {
var xList = (NbtList)x;
var yList = (NbtList)y;
// Lists are considered equal if their type, count, and contents are equal
return (xList.ListType == yList.ListType) &&
(xList.Count == yList.Count) &&
xList.SequenceEqual(yList, this);
} else {
// END and unknown
throw new ArgumentException("Cannot compare tags of type " + x.TagType);
}
}
private object GetRawValue(NbtTag tag) {
switch (tag.TagType) {
case NbtTagType.Byte:
return tag.ByteValue;
case NbtTagType.ByteArray:
return tag.ByteArrayValue;
case NbtTagType.Double:
return tag.DoubleValue;
case NbtTagType.Float:
return tag.FloatValue;
case NbtTagType.Int:
return tag.IntValue;
case NbtTagType.IntArray:
return tag.IntArrayValue;
case NbtTagType.Long:
return tag.LongValue;
case NbtTagType.Short:
return tag.ShortValue;
case NbtTagType.String:
return tag.StringValue;
default:
// returns null for List, Compound, and unknown tag types
return null;
}
}
}
Here is how you'd use it, for example:
private static void Main(string[] args) {
var comp1 = new NbtCompound("Foo") {
new NbtInt("IntField", 1),
new NbtString("StringVal", "blah"),
new NbtIntArray("ByteArrayVal", new[] { 1, 2, 3 }),
new NbtList("ListVal")
};
var comp2 = new NbtCompound("Bar") {
new NbtInt("IntField", 2),
new NbtString("StringVal", "blah"),
new NbtIntArray("ByteArrayVal", new[] { 1, 2, 3 }),
new NbtDouble("DoubleVal", 1.23)
};
PrintDifference(comp1, comp2, NbtComparer.Instance);
Console.ReadLine();
}
private static void PrintDifference(NbtCompound x, NbtCompound y, IEqualityComparer<NbtTag> comparer) {
if (comparer.Equals(x, y)) {
Console.WriteLine("They're equal!");
} else {
Console.WriteLine("They're NOT equal!");
if (!string.Equals(x.Name, y.Name)) {
Console.WriteLine("Names are different: {0} vs {1}", x.Name, y.Name);
}
// Compare named tags that are present in both compounds
foreach (var sharedName in x.Names.Intersect(y.Names)) {
if (comparer.Equals(x[sharedName], y[sharedName])) {
Console.WriteLine(" \"{0}\" is same ({1})", sharedName, x[sharedName]);
} else {
Console.WriteLine(" \"{0}\" is different ({1} vs {2})", sharedName, x[sharedName],
y[sharedName]);
}
}
// Print named tags that are unique to one of the compounds
foreach (var newName in x.Names.Except(y.Names)) {
Console.WriteLine(" \"{0}\" is only in {1} ({2})", newName, x.Name, x[newName]);
}
foreach (var newName in y.Names.Except(x.Names)) {
Console.WriteLine(" \"{0}\" is only in {1} ({2})", newName, y.Name, y[newName]);
}
}
}
Now that I think about it, tags with numeric values (byte, int, long, float, double) can also implement IComparable. Perhaps string tags too.
@fragmer, when is next release?
Well, I can do a minor release (0.6.4) with just these additions in a couple days.
Timeline of the next major release (either 0.7 or 1.0) is uncertain. The big new feature (automated serialization/deserialization of objects) has been stalled for a while. It needs well over 100 hours of additional work, and I don't have much spare time these days.
I added #18 and #19 to track progress on this feature.
Can you add method to compare two compounds?