Skip to content

Commit

Permalink
Reimplemented CompoundTag as a Dictionary
Browse files Browse the repository at this point in the history
  • Loading branch information
ForeverZer0 committed Aug 27, 2023
1 parent b50d5eb commit a4ecca8
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 26 deletions.
8 changes: 4 additions & 4 deletions SharpNBT/TagBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
}

Expand Down Expand Up @@ -426,9 +426,9 @@ public class Context: IDisposable
/// <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;
Expand Down
162 changes: 140 additions & 22 deletions SharpNBT/Tags/CompoundTag.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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>
Expand All @@ -34,7 +37,10 @@ public CompoundTag(string? name) : base(TagType.Compound, name)
/// <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>
Expand All @@ -44,8 +50,113 @@ public CompoundTag(string? name, IEnumerable<Tag> values) : this(name)
/// <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>
/// <returns>A string that represents the current object.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.ToString?view=netcore-5.0">`Object.ToString` on docs.microsoft.com</a></footer>
Expand All @@ -71,16 +182,16 @@ public string PrettyPrinted(string? indent = " ")
/// 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)
Expand All @@ -91,12 +202,6 @@ public string PrettyPrinted(string? indent = " ")
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)
{
Expand All @@ -106,7 +211,7 @@ protected internal override void PrettyPrinted(StringBuilder buffer, int level,

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 + "}");
}
Expand All @@ -118,13 +223,18 @@ protected internal override void PrettyPrinted(StringBuilder buffer, int level,
/// <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();

// TODO: Use StringBuilder

return $"{StringifyName}:{{{string.Join(',', strings)}}}";
var sb = new StringBuilder();
sb.Append($"{StringifyName}:{{");

var i = 0;
foreach (var value in dict.Values)
{
if (i++ > 0)
sb.Append(',');
sb.Append(value);
}
sb.Append('}');
return sb.ToString();
}

/// <summary>
Expand All @@ -138,4 +248,12 @@ public string Stringify(bool topLevel)
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;
}
}

0 comments on commit a4ecca8

Please sign in to comment.