From a4ecca89a869b1d940109ca5d36d179ff2393ad1 Mon Sep 17 00:00:00 2001 From: Eric Freed Date: Sun, 27 Aug 2023 04:03:33 -0400 Subject: [PATCH] Reimplemented CompoundTag as a Dictionary --- SharpNBT/TagBuilder.cs | 8 +- SharpNBT/Tags/CompoundTag.cs | 162 ++++++++++++++++++++++++++++++----- 2 files changed, 144 insertions(+), 26 deletions(-) diff --git a/SharpNBT/TagBuilder.cs b/SharpNBT/TagBuilder.cs index f60c13a..67ee0d8 100644 --- a/SharpNBT/TagBuilder.cs +++ b/SharpNBT/TagBuilder.cs @@ -16,7 +16,7 @@ namespace SharpNBT; public class TagBuilder { private readonly CompoundTag root; - private readonly Stack tree; + private readonly Stack> tree; /// /// 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(); + tree = new Stack>(); tree.Push(root); } @@ -426,9 +426,9 @@ public class TagBuilder /// /// Gets the top-level tag for this context. /// - public TagContainer Tag { get; } + public ICollection Tag { get; } - internal Context(TagContainer tag, CloseHandler handler) + internal Context(ICollection tag, CloseHandler handler) { Tag = tag; closeHandler = handler; diff --git a/SharpNBT/Tags/CompoundTag.cs b/SharpNBT/Tags/CompoundTag.cs index ad6f90c..fded242 100644 --- a/SharpNBT/Tags/CompoundTag.cs +++ b/SharpNBT/Tags/CompoundTag.cs @@ -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 does not require to be explicitly added, it will be added automatically during serialization. /// [PublicAPI][Serializable] -public class CompoundTag : TagContainer +public class CompoundTag : Tag, IDictionary, ICollection { + private readonly Dictionary dict; + /// /// Creates a new instance of the class. /// /// The name of the tag, or if tag has no name. public CompoundTag(string? name) : base(TagType.Compound, name) { - NamedChildren = true; - RequiredType = null; + dict = new Dictionary(); } /// @@ -34,7 +37,10 @@ public class CompoundTag : TagContainer /// A collection objects that are children of this object. public CompoundTag(string? name, IEnumerable values) : this(name) { - AddRange(values); + foreach (var value in values) + { + dict.Add(value.Name!, AssertName(value)); + } } /// @@ -44,8 +50,113 @@ public class CompoundTag : TagContainer /// The destination (see ) for this serialization. protected CompoundTag(SerializationInfo info, StreamingContext context) : base(info, context) { + var result = info.GetValue("children", typeof(Dictionary)) as Dictionary; + dict = result ?? new Dictionary(); } + /// + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue("children", dict); + } + + /// + void ICollection>.Add(KeyValuePair item) => dict.Add(item.Key, item.Value); + + /// + bool ICollection>.Contains(KeyValuePair item) => dict.Contains(item); + + /// + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + foreach (var kvp in dict) + array[arrayIndex++] = kvp; + } + + /// + bool ICollection>.IsReadOnly => false; + + /// + bool ICollection.IsReadOnly => false; + + /// + bool ICollection>.Remove(KeyValuePair item) => dict.Remove(item.Key); + + /// + public void Clear() => dict.Clear(); + + /// + public int Count => dict.Count; + + /// + IEnumerator> IEnumerable>.GetEnumerator() => dict.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => dict.GetEnumerator(); + + /// + public IEnumerator GetEnumerator() => dict.Values.GetEnumerator(); + + /// + public void CopyTo(Tag[] array, int arrayIndex) + { + foreach (var value in dict.Values) + array[arrayIndex++] = value; + } + + /// + public void Add(string key, Tag value) => dict.Add(key, AssertName(value)); + + /// + public void Add(Tag value) => dict.Add(value.Name!, AssertName(value)); + + /// + public bool ContainsKey(string key) => dict.ContainsKey(key); + + /// + public bool Contains(Tag tag) => !string.IsNullOrEmpty(tag.Name) && dict.ContainsKey(tag.Name); + + /// + public bool Remove(string key) => dict.Remove(key); + + /// + public bool Remove(Tag item) => !string.IsNullOrWhiteSpace(item.Name) && dict.Remove(item.Name); + + /// + public bool TryGetValue(string key, out Tag value) => dict.TryGetValue(key, out value!); + + /// + public bool TryGetValue(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; + } + + /// + public ICollection Keys => dict.Keys; + + /// + public ICollection Values => dict.Values; + + /// + public Tag this[string name] + { + get => dict[name]; + set => dict[name] = value; + } + + public TTag Get(string name) where TTag : Tag + { + return (TTag)dict[name]; + } + /// Returns a string that represents the current object. /// A string that represents the current object. /// @@ -71,16 +182,16 @@ public class CompoundTag : TagContainer /// Searches the children of this tag, returning the first child with the specified . /// /// The name of the tag to search for. - /// to recursively search children, otherwise to only search direct descendants. + /// to recursively search children, otherwise to only search direct descendants. /// The first tag found with , otherwise if none was found. - 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; } - /// - /// Retrieves a child tag with the specified , or if no match was found. - /// - /// The name of the tag to retrieve. - public Tag? this[string name] => Find(name, false); - /// 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 /// 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(); } /// @@ -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; + } } \ No newline at end of file