Reimplemented CompoundTag as a Dictionary

This commit is contained in:
Eric Freed 2023-08-27 04:03:33 -04:00
parent b50d5ebbd6
commit a4ecca89a8
2 changed files with 144 additions and 26 deletions

View File

@ -16,7 +16,7 @@ namespace SharpNBT;
public class TagBuilder public class TagBuilder
{ {
private readonly CompoundTag root; private readonly CompoundTag root;
private readonly Stack<TagContainer> tree; private readonly Stack<ICollection<Tag>> tree;
/// <summary> /// <summary>
/// Gets the zero-based depth of the current node, indicating how deeply nested it is within other tags. /// Gets the zero-based depth of the current node, indicating how deeply nested it is within other tags.
@ -32,7 +32,7 @@ public class TagBuilder
public TagBuilder(string? name = null) public TagBuilder(string? name = null)
{ {
root = new CompoundTag(name); root = new CompoundTag(name);
tree = new Stack<TagContainer>(); tree = new Stack<ICollection<Tag>>();
tree.Push(root); tree.Push(root);
} }
@ -426,9 +426,9 @@ public class TagBuilder
/// <summary> /// <summary>
/// Gets the top-level tag for this context. /// Gets the top-level tag for this context.
/// </summary> /// </summary>
public TagContainer Tag { get; } public ICollection<Tag> Tag { get; }
internal Context(TagContainer tag, CloseHandler handler) internal Context(ICollection<Tag> tag, CloseHandler handler)
{ {
Tag = tag; Tag = tag;
closeHandler = handler; closeHandler = handler;

View File

@ -1,6 +1,8 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -15,16 +17,17 @@ namespace SharpNBT;
/// closing <see cref="EndTag"/> does not require to be explicitly added, it will be added automatically during serialization. /// closing <see cref="EndTag"/> does not require to be explicitly added, it will be added automatically during serialization.
/// </remarks> /// </remarks>
[PublicAPI][Serializable] [PublicAPI][Serializable]
public class CompoundTag : TagContainer public class CompoundTag : Tag, IDictionary<string, Tag>, ICollection<Tag>
{ {
private readonly Dictionary<string, Tag> dict;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="CompoundTag"/> class. /// Creates a new instance of the <see cref="CompoundTag"/> class.
/// </summary> /// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param> /// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
public CompoundTag(string? name) : base(TagType.Compound, name) public CompoundTag(string? name) : base(TagType.Compound, name)
{ {
NamedChildren = true; dict = new Dictionary<string, Tag>();
RequiredType = null;
} }
/// <summary> /// <summary>
@ -34,7 +37,10 @@ public class CompoundTag : TagContainer
/// <param name="values">A collection <see cref="Tag"/> objects that are children of this object.</param> /// <param name="values">A collection <see cref="Tag"/> objects that are children of this object.</param>
public CompoundTag(string? name, IEnumerable<Tag> values) : this(name) public CompoundTag(string? name, IEnumerable<Tag> values) : this(name)
{ {
AddRange(values); foreach (var value in values)
{
dict.Add(value.Name!, AssertName(value));
}
} }
/// <summary> /// <summary>
@ -44,6 +50,111 @@ public class CompoundTag : TagContainer
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param> /// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected CompoundTag(SerializationInfo info, StreamingContext context) : base(info, context) protected CompoundTag(SerializationInfo info, StreamingContext context) : base(info, context)
{ {
var result = info.GetValue("children", typeof(Dictionary<string, Tag>)) as Dictionary<string, Tag>;
dict = result ?? new Dictionary<string, Tag>();
}
/// <inheritdoc />
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("children", dict);
}
/// <inheritdoc />
void ICollection<KeyValuePair<string, Tag>>.Add(KeyValuePair<string, Tag> item) => dict.Add(item.Key, item.Value);
/// <inheritdoc />
bool ICollection<KeyValuePair<string, Tag>>.Contains(KeyValuePair<string, Tag> item) => dict.Contains(item);
/// <inheritdoc />
void ICollection<KeyValuePair<string, Tag>>.CopyTo(KeyValuePair<string, Tag>[] array, int arrayIndex)
{
foreach (var kvp in dict)
array[arrayIndex++] = kvp;
}
/// <inheritdoc />
bool ICollection<KeyValuePair<string, Tag>>.IsReadOnly => false;
/// <inheritdoc />
bool ICollection<Tag>.IsReadOnly => false;
/// <inheritdoc />
bool ICollection<KeyValuePair<string, Tag>>.Remove(KeyValuePair<string, Tag> item) => dict.Remove(item.Key);
/// <inheritdoc cref="ICollection{T}.Clear"/>
public void Clear() => dict.Clear();
/// <inheritdoc cref="ICollection{T}.Clear"/>
public int Count => dict.Count;
/// <inheritdoc />
IEnumerator<KeyValuePair<string, Tag>> IEnumerable<KeyValuePair<string, Tag>>.GetEnumerator() => dict.GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => dict.GetEnumerator();
/// <inheritdoc />
public IEnumerator<Tag> GetEnumerator() => dict.Values.GetEnumerator();
/// <inheritdoc />
public void CopyTo(Tag[] array, int arrayIndex)
{
foreach (var value in dict.Values)
array[arrayIndex++] = value;
}
/// <inheritdoc />
public void Add(string key, Tag value) => dict.Add(key, AssertName(value));
/// <inheritdoc cref="ICollection{T}.Add"/>
public void Add(Tag value) => dict.Add(value.Name!, AssertName(value));
/// <inheritdoc />
public bool ContainsKey(string key) => dict.ContainsKey(key);
/// <inheritdoc />
public bool Contains(Tag tag) => !string.IsNullOrEmpty(tag.Name) && dict.ContainsKey(tag.Name);
/// <inheritdoc />
public bool Remove(string key) => dict.Remove(key);
/// <inheritdoc />
public bool Remove(Tag item) => !string.IsNullOrWhiteSpace(item.Name) && dict.Remove(item.Name);
/// <inheritdoc />
public bool TryGetValue(string key, out Tag value) => dict.TryGetValue(key, out value!);
/// <inheritdoc cref="TryGetValue"/>
public bool TryGetValue<TTag>(string key, out TTag value) where TTag : Tag
{
if (dict.TryGetValue(key, out var tag) && tag is TTag result)
{
value = result;
return true;
}
value = null!;
return false;
}
/// <inheritdoc />
public ICollection<string> Keys => dict.Keys;
/// <inheritdoc />
public ICollection<Tag> Values => dict.Values;
/// <inheritdoc />
public Tag this[string name]
{
get => dict[name];
set => dict[name] = value;
}
public TTag Get<TTag>(string name) where TTag : Tag
{
return (TTag)dict[name];
} }
/// <summary>Returns a string that represents the current object.</summary> /// <summary>Returns a string that represents the current object.</summary>
@ -71,16 +182,16 @@ public class CompoundTag : TagContainer
/// Searches the children of this tag, returning the first child with the specified <paramref name="name"/>. /// Searches the children of this tag, returning the first child with the specified <paramref name="name"/>.
/// </summary> /// </summary>
/// <param name="name">The name of the tag to search for.</param> /// <param name="name">The name of the tag to search for.</param>
/// <param name="deep"><see langword="true"/> to recursively search children, otherwise <see langword="false"/> to only search direct descendants.</param> /// <param name="recursive"><see langword="true"/> to recursively search children, otherwise <see langword="false"/> to only search direct descendants.</param>
/// <returns>The first tag found with <paramref name="name"/>, otherwise <see langword="null"/> if none was found.</returns> /// <returns>The first tag found with <paramref name="name"/>, otherwise <see langword="null"/> if none was found.</returns>
public Tag? Find(string name, bool deep) public Tag? Find(string name, bool recursive = false)
{ {
foreach (var tag in this) foreach (var tag in dict.Values)
{ {
if (string.CompareOrdinal(name, tag.Name) == 0) if (string.CompareOrdinal(name, tag.Name) == 0)
return tag; return tag;
if (deep && tag is CompoundTag child) if (recursive && tag is CompoundTag child)
{ {
var result = child.Find(name, true); var result = child.Find(name, true);
if (result != null) if (result != null)
@ -91,12 +202,6 @@ public class CompoundTag : TagContainer
return null; return null;
} }
/// <summary>
/// Retrieves a child tag with the specified <paramref name="name"/>, or <see langword="null"/> if no match was found.
/// </summary>
/// <param name="name">The name of the tag to retrieve.</param>
public Tag? this[string name] => Find(name, false);
/// <inheritdoc cref="Tag.PrettyPrinted(StringBuilder,int,string)"/> /// <inheritdoc cref="Tag.PrettyPrinted(StringBuilder,int,string)"/>
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent) protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{ {
@ -106,7 +211,7 @@ public class CompoundTag : TagContainer
buffer.AppendLine(space + ToString()); buffer.AppendLine(space + ToString());
buffer.AppendLine(space + "{"); buffer.AppendLine(space + "{");
foreach (var tag in this) foreach (var tag in dict.Values)
tag.PrettyPrinted(buffer, level + 1, indent); tag.PrettyPrinted(buffer, level + 1, indent);
buffer.AppendLine(space + "}"); buffer.AppendLine(space + "}");
} }
@ -118,13 +223,18 @@ public class CompoundTag : TagContainer
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/> /// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() public override string Stringify()
{ {
var strings = new string[Count]; var sb = new StringBuilder();
for (var i = 0; i < strings.Length; i++) sb.Append($"{StringifyName}:{{");
strings[i] = this[i].Stringify();
// TODO: Use StringBuilder var i = 0;
foreach (var value in dict.Values)
return $"{StringifyName}:{{{string.Join(',', strings)}}}"; {
if (i++ > 0)
sb.Append(',');
sb.Append(value);
}
sb.Append('}');
return sb.ToString();
} }
/// <summary> /// <summary>
@ -138,4 +248,12 @@ public class CompoundTag : TagContainer
var str = Stringify(); var str = Stringify();
return topLevel ? $"{{{str}}}" : str; return topLevel ? $"{{{str}}}" : str;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Tag AssertName(Tag tag)
{
if (string.IsNullOrWhiteSpace(tag.Name))
throw new FormatException(Strings.ChildrenMustBeNamed);
return tag;
}
} }