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
{
private readonly CompoundTag root;
private readonly Stack<TagContainer> tree;
private readonly Stack<ICollection<Tag>> tree;
/// <summary>
/// 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)
{
root = new CompoundTag(name);
tree = new Stack<TagContainer>();
tree = new Stack<ICollection<Tag>>();
tree.Push(root);
}
@ -426,9 +426,9 @@ public class TagBuilder
/// <summary>
/// Gets the top-level tag for this context.
/// </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;
closeHandler = handler;

View File

@ -1,6 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
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.
/// </remarks>
[PublicAPI][Serializable]
public class CompoundTag : TagContainer
public class CompoundTag : Tag, IDictionary<string, Tag>, ICollection<Tag>
{
private readonly Dictionary<string, Tag> dict;
/// <summary>
/// Creates a new instance of the <see cref="CompoundTag"/> class.
/// </summary>
/// <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)
{
NamedChildren = true;
RequiredType = null;
dict = new Dictionary<string, Tag>();
}
/// <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>
public CompoundTag(string? name, IEnumerable<Tag> values) : this(name)
{
AddRange(values);
foreach (var value in values)
{
dict.Add(value.Name!, AssertName(value));
}
}
/// <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>
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>
@ -71,16 +182,16 @@ public class CompoundTag : TagContainer
/// Searches the children of this tag, returning the first child with the specified <paramref name="name"/>.
/// </summary>
/// <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>
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)
return tag;
if (deep && tag is CompoundTag child)
if (recursive && tag is CompoundTag child)
{
var result = child.Find(name, true);
if (result != null)
@ -91,12 +202,6 @@ public class CompoundTag : TagContainer
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)"/>
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 + "{");
foreach (var tag in this)
foreach (var tag in dict.Values)
tag.PrettyPrinted(buffer, level + 1, indent);
buffer.AppendLine(space + "}");
}
@ -118,13 +223,18 @@ public class CompoundTag : TagContainer
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify()
{
var strings = new string[Count];
for (var i = 0; i < strings.Length; i++)
strings[i] = this[i].Stringify();
var sb = new StringBuilder();
sb.Append($"{StringifyName}:{{");
// TODO: Use StringBuilder
return $"{StringifyName}:{{{string.Join(',', strings)}}}";
var i = 0;
foreach (var value in dict.Values)
{
if (i++ > 0)
sb.Append(',');
sb.Append(value);
}
sb.Append('}');
return sb.ToString();
}
/// <summary>
@ -138,4 +248,12 @@ public class CompoundTag : TagContainer
var str = Stringify();
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;
}
}