refactoring and optimization

This commit is contained in:
Eric Freed 2023-08-27 03:13:05 -04:00
parent 05126cee2a
commit b50d5ebbd6
20 changed files with 369 additions and 188 deletions

View File

@ -42,7 +42,7 @@ public class TagBuilder
/// <param name="name">The name of the node to add.</param>
/// <param name="value">The value of the tag.</param>
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
public TagBuilder AddBool(string? name, bool value) => AddTag(new BoolTag(name, value));
public TagBuilder AddBool(string? name, bool value) => AddTag(new ByteTag(name, value));
/// <summary>
/// Adds a new unnamed <see cref="ByteTag"/> with the specified <paramref name="value"/> to the tree at the current depth.
@ -58,7 +58,10 @@ public class TagBuilder
/// <param name="value">The value of the tag.</param>
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
public TagBuilder AddByte(string? name, byte value) => AddTag(new ByteTag(name, value));
/// <inheritdoc cref="AddByte(string,byte)"/>
public TagBuilder AddByte(string? name, int value) => AddByte(name, unchecked((byte)(value & 0xFF)));
/// <inheritdoc cref="AddByte(string,byte)"/>
[CLSCompliant(false)]
public TagBuilder AddByte(string? name, sbyte value) => AddByte(name, unchecked((byte)value));
@ -70,6 +73,9 @@ public class TagBuilder
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
public TagBuilder AddByte(byte value) => AddByte(null, value);
/// <inheritdoc cref="AddByte(sbyte)"/>
public TagBuilder AddByte(int value) => AddByte(null, unchecked((byte)(value & 0xFF)));
/// <inheritdoc cref="AddByte(sbyte)"/>
[CLSCompliant(false)]
public TagBuilder AddByte(sbyte value) => AddByte(null, unchecked((byte)value));
@ -82,6 +88,9 @@ public class TagBuilder
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
public TagBuilder AddShort(string? name, short value) => AddTag(new ShortTag(name, value));
/// <inheritdoc cref="AddShort(string,short)"/>
public TagBuilder AddShort(string? name, int value) => AddShort(name, unchecked((short)(value & 0xFFFF)));
/// <inheritdoc cref="AddShort(string,short)"/>
[CLSCompliant(false)]
public TagBuilder AddShort(string? name, ushort value) => AddShort(name, unchecked((short)value));
@ -93,6 +102,9 @@ public class TagBuilder
/// <returns>Returns this <see cref="TagBuilder"/> instance for chaining.</returns>
public TagBuilder AddShort(short value) => AddShort(null, value);
/// <inheritdoc cref="AddShort(short)"/>
public TagBuilder AddShort(int value) => AddShort(null, unchecked((short)(value & 0xFFFF)));
/// <inheritdoc cref="AddShort(short)"/>
[CLSCompliant(false)]
public TagBuilder AddShort(ushort value) => AddShort(null, unchecked((short)value));
@ -236,8 +248,7 @@ public class TagBuilder
/// <inheritdoc cref="AddByteArray(byte[])"/>
[CLSCompliant(false)]
public TagBuilder AddByteArray(IEnumerable<sbyte> values) => AddByteArray(null, values.ToArray());
/// <summary>
/// Adds a new <see cref="IntArrayTag"/> with the specified <paramref name="values"/> to the tree at the current depth.
/// </summary>

View File

@ -118,7 +118,7 @@ public class TagWriter : TagIO
public virtual void WriteByteArray(ByteArrayTag tag)
{
WriteTypeAndName(tag);
WriteCount(tag);
WriteCount(tag.Count);
BaseStream.Write(tag.ToArray(), 0, tag.Count);
}
@ -129,7 +129,7 @@ public class TagWriter : TagIO
public virtual void WriteIntArray(IntArrayTag tag)
{
WriteTypeAndName(tag);
WriteCount(tag);
WriteCount(tag.Count);
var values = new Span<int>(tag.ToArray());
if (UseVarInt)
@ -155,7 +155,7 @@ public class TagWriter : TagIO
{
WriteTypeAndName(tag);
WriteCount(tag);
WriteCount(tag.Count);
var values = new Span<long>(tag.ToArray());
if (UseVarInt)
@ -182,7 +182,7 @@ public class TagWriter : TagIO
{
WriteTypeAndName(tag);
BaseStream.WriteByte((byte) tag.ChildType);
WriteCount(tag);
WriteCount(tag.Count);
foreach (var child in tag)
WriteTag(child);
@ -377,11 +377,11 @@ public class TagWriter : TagIO
return bytes;
}
private void WriteCount<T>(EnumerableTag<T> tag)
private void WriteCount(int count)
{
if (UseVarInt)
VarInt.Write(BaseStream, tag.Count, ZigZagEncoding);
VarInt.Write(BaseStream, count, ZigZagEncoding);
else
BaseStream.Write(GetBytes(tag.Count), 0, sizeof(int));
BaseStream.Write(GetBytes(count), 0, sizeof(int));
}
}

View File

@ -1,6 +1,142 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using JetBrains.Annotations;
namespace SharpNBT;
public class ArrayTag
/// <summary>
/// Base class for NBT tags that contain a fixed-size array of numeric types.
/// </summary>
/// <typeparam name="T">A value type that implements <see cref="INumber{TSelf}"/>.</typeparam>
[PublicAPI][Serializable]
public abstract class ArrayTag<T> : Tag, IReadOnlyList<T> where T : unmanaged, INumber<T>
{
/// <summary>
/// Gets a <see cref="Span{T}"/> over the tag data.
/// </summary>
public Span<T> Span => new(array);
/// <summary>
/// Gets a <see cref="Memory{T}"/> over the tag data.
/// </summary>
public Memory<T> Memory => new(array);
/// <inheritdoc />
/// <param name="value">The value of the tag.</param>
// ReSharper disable InvalidXmlDocComment
protected ArrayTag(TagType type, string? name, T[] value) : base(type, name)
// ReSharper restore InvalidXmlDocComment
{
array = value;
}
/// <inheritdoc />
protected ArrayTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
var _ = info.GetInt32("count");
var value = info.GetValue("values", typeof(T[])) as T[];
array = value ?? Array.Empty<T>();
}
/// <inheritdoc />
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("count", array.Length);
info.AddValue("values", array);
}
/// <inheritdoc />
public IEnumerator<T> GetEnumerator()
{
// ReSharper disable ForCanBeConvertedToForeach
for (var i = 0; i < array.Length; i++)
yield return array[i];
// ReSharper restore ForCanBeConvertedToForeach
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => array.GetEnumerator();
/// <inheritdoc cref="IList{T}.CopyTo"/>
// ReSharper disable once ParameterHidesMember
public void CopyTo(T[] array, int arrayIndex) => this.array.CopyTo(array, arrayIndex);
/// <inheritdoc />
public int Count => array.Length;
/// <inheritdoc cref="IList{T}.IndexOf"/>
public int IndexOf(T item)
{
for (var i = 0; i < array.Length; i++)
{
if (array[i] == item)
return i;
}
return -1;
}
/// <inheritdoc cref="Span{T}.Slice(int,int)"/>
/// <remarks>This method being defined provides Range indexers for the class.</remarks>
public Span<T> Slice(int start, int length) => new(array, start, length);
/// <inheritdoc cref="IList{T}.this"/>
public T this[int index]
{
get => array[index];
set => array[index] = value;
}
/// <summary>
/// Returns a reference to the underlying memory of this object that is be pinned using the <see langword="fixed"/>
/// statement.
/// </summary>
/// <returns>A reference to the first value in the underlying array.</returns>
public ref T GetPinnableReference() => ref array[0] ;
private protected string Stringify(char prefix, char? suffix)
{
var sb = new StringBuilder(32 + array.Length * 4);
sb.Append($"{StringifyName}:[{prefix};");
for (var i = 0; i < array.Length; i++)
{
if (i > 0)
sb.Append(',');
sb.Append(array[i]);
if (suffix != null)
sb.Append(suffix.Value);
}
sb.Append(']');
return sb.ToString();
}
/// <summary>
/// Implicit conversion of a an <see cref="ArrayTag{T}"/> to an array of <see cref="T"/>.
/// </summary>
/// <param name="tag">The <see cref="ArrayTag{T}"/> to be converted.</param>
/// <returns>The value of <paramref name="tag"/> as an array of <see cref="T"/>.</returns>
public static implicit operator T[](ArrayTag<T> tag) => tag.array;
/// <summary>
/// Implicit conversion of a an <see cref="ArrayTag{T}"/> to a <see cref="Span{T}"/>.
/// </summary>
/// <param name="tag">The <see cref="ArrayTag{T}"/> to be converted.</param>
/// <returns>The value of <paramref name="tag"/> as a <see cref="Span{T}"/>.</returns>
public static implicit operator Span<T>(ArrayTag<T> tag) => new(tag.array);
/// <summary>
/// Implicit conversion of a an <see cref="ArrayTag{T}"/> to a <see cref="Memory{T}"/>.
/// </summary>
/// <param name="tag">The <see cref="ArrayTag{T}"/> to be converted.</param>
/// <returns>The value of <paramref name="tag"/> as a <see cref="Memory{T}"/>.</returns>
public static implicit operator Memory<T>(ArrayTag<T> tag) => new(tag.array);
private readonly T[] array;
}

View File

@ -12,19 +12,19 @@ namespace SharpNBT;
/// actually serialized as.
/// </remarks>
[PublicAPI][Serializable]
[Obsolete("Use the IsBool and Bool properties of ByteTag. This class will be removed in a future release.")]
public class BoolTag : Tag<bool>
[Obsolete("Use the IsBool and Bool properties of ByteTag. This class will be removed in a future version.")]
public class BoolTag : Tag
{
private const string TRUE = "true";
private const string FALSE = "false";
public bool Value { get; set; }
/// <summary>
/// Creates a new instance of the <see cref="SharpNBT.ByteTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
public BoolTag(string? name, bool value) : base(TagType.Byte, name, value)
public BoolTag(string? name, bool value) : base(TagType.Byte, name)
{
Value = value;
}
/// <summary>
@ -37,7 +37,7 @@ public class BoolTag : Tag<bool>
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_Bool({PrettyName}): {(Value ? TRUE : FALSE)}";
public override string ToString() => $"TAG_Byte({PrettyName}): {(Value ? "true" : "false")}";
/// <summary>
/// Implicit conversion of this tag to a <see cref="byte"/>.
@ -51,6 +51,6 @@ public class BoolTag : Tag<bool>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{(Value ? TRUE : FALSE)}";
public override string Stringify() => $"{StringifyName}:{(Value ? "true" : "false")}";
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using JetBrains.Annotations;
@ -13,8 +14,17 @@ namespace SharpNBT;
/// the bits are equivalent for your values.
/// </remarks>
[PublicAPI][Serializable]
public class ByteArrayTag : EnumerableTag<byte>
public class ByteArrayTag : ArrayTag<byte>
{
/// <summary>
/// Initializes a new instance of the <see cref="ByteArrayTag"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="capacity">The capacity of the array.</param>
public ByteArrayTag(string? name, int capacity) : base(TagType.IntArray, name, new byte[capacity])
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ByteArrayTag"/>.
/// </summary>
@ -29,7 +39,7 @@ public class ByteArrayTag : EnumerableTag<byte>
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
public ByteArrayTag(string? name, IEnumerable<byte> values) : base(TagType.ByteArray, name, values)
public ByteArrayTag(string? name, IEnumerable<byte> values) : base(TagType.ByteArray, name, values.ToArray())
{
}
@ -38,7 +48,7 @@ public class ByteArrayTag : EnumerableTag<byte>
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
public ByteArrayTag(string? name, ReadOnlySpan<byte> values) : base(TagType.ByteArray, name, values)
public ByteArrayTag(string? name, ReadOnlySpan<byte> values) : base(TagType.ByteArray, name, values.ToArray())
{
}
@ -63,11 +73,5 @@ public class ByteArrayTag : EnumerableTag<byte>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify()
{
var values = new string[Count];
for (var i = 0; i < Count; i++)
values[i] = $"{this[i]}b";
return $"{StringifyName}[B;{string.Join(',', values)}]";
}
public override string Stringify() => Stringify('B', 'b');
}

View File

@ -13,7 +13,7 @@ namespace SharpNBT;
/// equivalent.
/// </remarks>
[PublicAPI][Serializable]
public class ByteTag : Tag<byte>
public class ByteTag : NumericTag<byte>
{
/// <summary>
/// Gets a flag indicating if this <see cref="ByteTag"/> was assigned a <see cref="bool"/> value.
@ -95,9 +95,13 @@ public class ByteTag : Tag<byte>
protected ByteTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_Byte({PrettyName}): {Value}";
public override string ToString()
{
object obj = IsBool ? Bool : Value;
return $"TAG_Byte({PrettyName}): {obj}";
}
/// <summary>
/// Implicit conversion of this tag to a <see cref="byte"/>.
@ -126,5 +130,5 @@ public class ByteTag : Tag<byte>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value}B";
public override string Stringify() => $"{StringifyName}:{Value}B";
}

View File

@ -122,7 +122,9 @@ public class CompoundTag : TagContainer
for (var i = 0; i < strings.Length; i++)
strings[i] = this[i].Stringify();
return $"{StringifyName}{{{string.Join(',', strings)}}}";
// TODO: Use StringBuilder
return $"{StringifyName}:{{{string.Join(',', strings)}}}";
}
/// <summary>

View File

@ -8,7 +8,7 @@ namespace SharpNBT;
/// A tag that contains a single IEEE-754 double-precision floating point number.
/// </summary>
[PublicAPI][Serializable]
public class DoubleTag : Tag<double>
public class DoubleTag : NumericTag<double>
{
/// <summary>
/// Creates a new instance of the <see cref="DoubleTag"/> class with the specified <paramref name="value"/>.
@ -43,5 +43,5 @@ public class DoubleTag : Tag<double>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value:0.0}D";
public override string Stringify() => $"{StringifyName}:{Value:0.0}D";
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using System.Text;
using JetBrains.Annotations;
@ -72,7 +71,9 @@ public abstract class EnumerableTag<T> : Tag, IList<T>
protected EnumerableTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
var dummy = info.GetInt32("count");
internalList.AddRange((T[]) info.GetValue("values", typeof(T[])));
var obj = info.GetValue("values", typeof(T[])) as T[];
if (obj is IEnumerable<T> e)
internalList.AddRange(e);
}
/// <summary>Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with the data needed to serialize the target object.</summary>
@ -101,7 +102,7 @@ public abstract class EnumerableTag<T> : Tag, IList<T>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Add?view=netcore-5.0">`ICollection.Add` on docs.microsoft.com</a></footer>
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
public virtual void Add([DisallowNull] T item) => internalList.Add(item);
public virtual void Add(T item) => internalList.Add(item);
/// <summary>
/// Adds the elements of the specified collection to the <see cref="EnumerableTag{T}"/>.
@ -121,7 +122,7 @@ public abstract class EnumerableTag<T> : Tag, IList<T>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.Insert?view=netcore-5.0">`IList.Insert` on docs.microsoft.com</a></footer>
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
public virtual void Insert(int index, [DisallowNull] T item) => internalList.Insert(index, item);
public virtual void Insert(int index, T item) => internalList.Insert(index, item);
/// <summary>Gets or sets the element at the specified index.</summary>
/// <param name="index">The zero-based index of the element to get or set.</param>
@ -130,7 +131,6 @@ public abstract class EnumerableTag<T> : Tag, IList<T>
/// <exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <returns>The element at the specified index.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.Item?view=netcore-5.0">`IList.Item` on docs.microsoft.com</a></footer>
[DisallowNull]
public virtual T this[int index]
{
get => internalList[index];

View File

@ -8,7 +8,7 @@ namespace SharpNBT;
/// A tag that contains a single IEEE-754 single-precision floating point number.
/// </summary>
[PublicAPI][Serializable]
public class FloatTag : Tag<float>
public class FloatTag : NumericTag<float>
{
/// <summary>
/// Creates a new instance of the <see cref="FloatTag"/> class with the specified <paramref name="value"/>.
@ -42,5 +42,5 @@ public class FloatTag : Tag<float>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value:0.0}F";
public override string Stringify() => $"{StringifyName}:{Value:0.0}F";
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using JetBrains.Annotations;
@ -9,15 +10,17 @@ namespace SharpNBT;
/// A tag that whose value is a contiguous sequence of 32-bit integers.
/// </summary>
[PublicAPI][Serializable]
public class IntArrayTag : EnumerableTag<int>
public class IntArrayTag : ArrayTag<int>
{
/// <summary>
/// Initializes a new instance of the <see cref="IntArrayTag"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
public IntArrayTag(string? name) : base(TagType.IntArray, name)
/// <param name="capacity">The capacity of the array.</param>
public IntArrayTag(string? name, int capacity) : base(TagType.IntArray, name, new int[capacity])
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IntArrayTag"/> with the specified <paramref name="values"/>.
/// </summary>
@ -33,7 +36,7 @@ public class IntArrayTag : EnumerableTag<int>
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
public IntArrayTag(string? name, IEnumerable<int> values) : base(TagType.IntArray, name, values)
public IntArrayTag(string? name, IEnumerable<int> values) : base(TagType.IntArray, name, values.ToArray())
{
}
@ -42,7 +45,7 @@ public class IntArrayTag : EnumerableTag<int>
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
public IntArrayTag(string? name, ReadOnlySpan<int> values) : base(TagType.IntArray, name, values)
public IntArrayTag(string? name, ReadOnlySpan<int> values) : base(TagType.IntArray, name, values.ToArray())
{
}
@ -67,5 +70,5 @@ public class IntArrayTag : EnumerableTag<int>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}[I;{string.Join(',', this)}]";
public override string Stringify() => Stringify('I', null);
}

View File

@ -8,7 +8,7 @@ namespace SharpNBT;
/// A tag that contains a single 32-bit integer value.
/// </summary>
[PublicAPI][Serializable]
public class IntTag : Tag<int>
public class IntTag : NumericTag<int>
{
/// <summary>
/// Gets or sets the value of this tag as an unsigned value.
@ -70,5 +70,5 @@ public class IntTag : Tag<int>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value}";
public override string Stringify() => $"{StringifyName}:{Value}";
}

View File

@ -96,6 +96,8 @@ public class ListTag : TagContainer
for (var i = 0; i < strings.Length; i++)
strings[i] = this[i].Stringify();
return $"{StringifyName}[{string.Join(',', strings)}]";
// TODO: Use StringBuilder
return $"{StringifyName}:[{string.Join(',', strings)}]";
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using JetBrains.Annotations;
@ -9,13 +10,14 @@ namespace SharpNBT;
/// A tag that whose value is a contiguous sequence of 64-bit integers.
/// </summary>
[PublicAPI][Serializable]
public class LongArrayTag : EnumerableTag<long>
public class LongArrayTag : ArrayTag<long>
{
/// <summary>
/// Initializes a new instance of the <see cref="LongArrayTag"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
public LongArrayTag(string? name) : base(TagType.LongArray, name)
/// <param name="capacity">The capacity of the array.</param>
public LongArrayTag(string? name, int capacity) : base(TagType.LongArray, name, new long[capacity])
{
}
/// <summary>
@ -23,7 +25,7 @@ public class LongArrayTag : EnumerableTag<long>
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
public LongArrayTag(string? name, long[] values) : base(TagType.LongArray, name, values)
public LongArrayTag(string? name, long[] values) : base(TagType.LongArray, name, values.ToArray())
{
}
@ -41,7 +43,7 @@ public class LongArrayTag : EnumerableTag<long>
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
public LongArrayTag(string? name, IEnumerable<long> values) : base(TagType.LongArray, name, values)
public LongArrayTag(string? name, IEnumerable<long> values) : base(TagType.LongArray, name, values.ToArray())
{
}
@ -50,7 +52,7 @@ public class LongArrayTag : EnumerableTag<long>
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
public LongArrayTag(string? name, ReadOnlySpan<long> values) : base(TagType.LongArray, name, values)
public LongArrayTag(string? name, ReadOnlySpan<long> values) : base(TagType.LongArray, name, values.ToArray())
{
}
@ -66,11 +68,5 @@ public class LongArrayTag : EnumerableTag<long>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify()
{
var values = new string[Count];
for (var i = 0; i < Count; i++)
values[i] = $"{this[i]}l";
return $"{StringifyName}[L;{string.Join(',', values)}]";
}
public override string Stringify() => Stringify('L', 'l');
}

View File

@ -8,7 +8,7 @@ namespace SharpNBT;
/// A tag that contains a single 64-bit integer value.
/// </summary>
[PublicAPI][Serializable]
public class LongTag : Tag<long>
public class LongTag : NumericTag<long>
{
/// <summary>
/// Gets or sets the value of this tag as an unsigned value.
@ -70,5 +70,5 @@ public class LongTag : Tag<long>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value}L";
public override string Stringify() => $"{StringifyName}:{Value}L";
}

View File

@ -13,19 +13,32 @@ namespace SharpNBT;
[PublicAPI][Serializable]
public abstract class NumericTag<T> : Tag, IEquatable<NumericTag<T>>, IComparable<NumericTag<T>>, IComparable where T : unmanaged, INumber<T>
{
public T Value { get; set; }
/// <summary>
/// Gets or sets the value of the tag.
/// </summary>
public T Value { get; [Obsolete("Numeric tag types will be made immutable in a future version.")] set; }
/// <inheritdoc />
protected NumericTag(TagType type, string? name, T value) : base(type, name)
{
Value = value;
}
/// <inheritdoc />
protected NumericTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
var value = info.GetValue("value", typeof(T));
Value = value is null ? default : (T)value;
}
/// <inheritdoc />
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("value", Value, typeof(T));
}
/// <inheritdoc />
public bool Equals(NumericTag<T>? other)
{
if (ReferenceEquals(null, other)) return false;
@ -33,6 +46,7 @@ public abstract class NumericTag<T> : Tag, IEquatable<NumericTag<T>>, IComparabl
return base.Equals(other) && Value.Equals(other.Value);
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
@ -41,8 +55,10 @@ public abstract class NumericTag<T> : Tag, IEquatable<NumericTag<T>>, IComparabl
return Equals((NumericTag<T>)obj);
}
/// <inheritdoc />
public override int GetHashCode() => base.GetHashCode();
/// <inheritdoc />
public int CompareTo(NumericTag<T>? other)
{
if (ReferenceEquals(this, other)) return 0;
@ -50,6 +66,7 @@ public abstract class NumericTag<T> : Tag, IEquatable<NumericTag<T>>, IComparabl
return Value.CompareTo(other.Value);
}
/// <inheritdoc />
public int CompareTo(object? obj)
{
if (ReferenceEquals(null, obj)) return 1;
@ -57,27 +74,80 @@ public abstract class NumericTag<T> : Tag, IEquatable<NumericTag<T>>, IComparabl
return obj is NumericTag<T> other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(NumericTag<T>)}");
}
/// <summary>
/// Compares two values to determine equality.
/// </summary>
/// <param name="left">The value to compare with <paramref name="right" />.</param>
/// <param name="right">The value to compare with <paramref name="left" />.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> is equal to <paramref name="right" />; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator ==(NumericTag<T>? left, NumericTag<T>? right) => Equals(left, right);
/// <summary>
/// Compares two values to determine inequality.
/// </summary>
/// <param name="left">The value to compare with <paramref name="right" />.</param>
/// <param name="right">The value to compare with <paramref name="left" />.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> is not equal to <paramref name="right" />; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator !=(NumericTag<T>? left, NumericTag<T>? right) => !Equals(left, right);
/// <summary>Compares two values to determine which is less.</summary>
/// <param name="left">The value to compare with <paramref name="right" />.</param>
/// <param name="right">The value to compare with <paramref name="left" />.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> is less than <paramref name="right" />; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator <(NumericTag<T>? left, NumericTag<T>? right)
{
return Comparer<NumericTag<T>>.Default.Compare(left, right) < 0;
}
/// <summary>Compares two values to determine which is greater.</summary>
/// <param name="left">The value to compare with <paramref name="right" />.</param>
/// <param name="right">The value to compare with <paramref name="left" />.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> is greater than <paramref name="right" />; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator >(NumericTag<T>? left, NumericTag<T>? right)
{
return Comparer<NumericTag<T>>.Default.Compare(left, right) > 0;
}
/// <summary>Compares two values to determine which is less or equal.</summary>
/// <param name="left">The value to compare with <paramref name="right" />.</param>
/// <param name="right">The value to compare with <paramref name="left" />.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> is less than or equal to <paramref name="right" />;
/// otherwise, <see langword="false" />.
/// </returns>
public static bool operator <=(NumericTag<T>? left, NumericTag<T>? right)
{
return Comparer<NumericTag<T>>.Default.Compare(left, right) <= 0;
}
/// <summary>Compares two values to determine which is greater or equal.</summary>
/// <param name="left">The value to compare with <paramref name="right" />.</param>
/// <param name="right">The value to compare with <paramref name="left" />.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> is greater than or equal to <paramref name="right" />;
/// otherwise, <see langword="false" />.
/// </returns>
public static bool operator >=(NumericTag<T>? left, NumericTag<T>? right)
{
return Comparer<NumericTag<T>>.Default.Compare(left, right) >= 0;
}
/// <summary>
/// Implicit conversion of a an <see cref="NumericTag{T}"/> to a <see cref="T"/>.
/// </summary>
/// <param name="tag">The <see cref="NumericTag{T}"/> to be converted.</param>
/// <returns>The value of <paramref name="tag"/> as a <see cref="T"/>.</returns>
public static implicit operator T(NumericTag<T> tag) => tag.Value;
}

View File

@ -8,7 +8,7 @@ namespace SharpNBT;
/// A tag that contains a single 16-bit integer value.
/// </summary>
[PublicAPI][Serializable]
public class ShortTag : Tag<short>
public class ShortTag : NumericTag<short>
{
/// <summary>
/// Gets or sets the value of this tag as an unsigned value.
@ -76,5 +76,5 @@ public class ShortTag : Tag<short>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}{Value}S";
public override string Stringify() => $"{StringifyName}:{Value}S";
}

View File

@ -1,4 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.Serialization;
using JetBrains.Annotations;
@ -8,26 +9,36 @@ namespace SharpNBT;
/// A tag the contains a UTF-8 string.
/// </summary>
[PublicAPI][Serializable]
public class StringTag : Tag<string>
public class StringTag : Tag, IEquatable<StringTag>
{
/// <summary>
/// Gets or sets the value of the tag.
/// </summary>
public string Value { get; [Obsolete("String tag type will be made immutable in a future version.")] set; }
/// <summary>
/// Creates a new instance of the <see cref="StringTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
public StringTag(string? name, string? value) : base(TagType.String, name, value)
public StringTag(string? name, string? value) : base(TagType.String, name)
{
Value = value ?? string.Empty;
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
/// <inheritdoc />
protected StringTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
Value = info.GetString("value") ?? string.Empty;
}
/// <inheritdoc />
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("value", Value);
}
/// <inheritdoc cref="object.ToString"/>
public override string ToString() => $"TAG_String({PrettyName}): \"{Value}\"";
@ -43,5 +54,46 @@ public class StringTag : Tag<string>
/// </summary>
/// <returns>This NBT tag in SNBT format.</returns>
/// <seealso href="https://minecraft.fandom.com/wiki/NBT_format#SNBT_format"/>
public override string Stringify() => $"{StringifyName}\"{Value}\"";
public override string Stringify() => $"{StringifyName}:\"{Value}\""; // TODO: Does this get properly escaped?
/// <inheritdoc />
public bool Equals(StringTag? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return base.Equals(other) && string.CompareOrdinal(Value, other.Value) == 0;
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj.GetType() == GetType() && Equals((StringTag)obj);
}
/// <inheritdoc />
public override int GetHashCode() => base.GetHashCode(); // TODO: Add Value once immutable
/// <summary>
/// Compares two values to determine equality.
/// </summary>
/// <param name="left">The value to compare with <paramref name="right" />.</param>
/// <param name="right">The value to compare with <paramref name="left" />.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> is equal to <paramref name="right" />; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator ==(StringTag? left, StringTag? right) => Equals(left, right);
/// <summary>
/// Compares two values to determine inequality.
/// </summary>
/// <param name="left">The value to compare with <paramref name="right" />.</param>
/// <param name="right">The value to compare with <paramref name="left" />.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> is not equal to <paramref name="right" />; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator !=(StringTag? left, StringTag? right) => !Equals(left, right);
}

View File

@ -32,7 +32,8 @@ public abstract class Tag : IEquatable<Tag>, ISerializable, ICloneable
return new[]
{
typeof(TagType),
typeof(Tag<>),
typeof(NumericTag<>),
typeof(ArrayTag<>),
typeof(Tag[]),
typeof(EnumerableTag<>),
typeof(TagContainer),
@ -81,15 +82,20 @@ public abstract class Tag : IEquatable<Tag>, ISerializable, ICloneable
Type = type;
Name = name;
}
/// <summary>
/// Writes this tag as a formatted string to the given <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">A <see cref="StringBuilder"/> instance to write to.</param>
/// <param name="level">The current indent depth to write at.</param>
/// <param name="indent">The string to use for indents.</param>
protected internal abstract void PrettyPrinted(StringBuilder buffer, int level, string indent);
protected internal virtual void PrettyPrinted(StringBuilder buffer, int level, string indent)
{
for (var i = 0; i < level; i++)
buffer.Append(indent);
buffer.AppendLine(ToString());
}
/// <summary>
/// Gets the name of the object as a human-readable quoted string, or a default name to indicate it has no name when applicable.
/// </summary>
@ -234,113 +240,7 @@ public abstract class Tag : IEquatable<Tag>, ISerializable, ICloneable
{
if (string.IsNullOrEmpty(Name))
return string.Empty;
return simpleNameMatcher.IsMatch(Name) ? $"{Name}: " : $"\"{Name}\": ";
return simpleNameMatcher.IsMatch(Name) ? Name : $"\"{Name}\"";
}
}
}
/// <summary>
/// Abstract base class for <see cref="Tag"/> types that contain a single primitive value.
/// </summary>
/// <typeparam name="T">The type of the value the tag represents.</typeparam>
[PublicAPI][Serializable]
public abstract class Tag<T> : Tag, IEquatable<Tag<T>>
{
/// <summary>
/// Gets or sets the value of the tag.
/// </summary>
public T Value { get; set; }
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected Tag(SerializationInfo info, StreamingContext context) : base(info, context)
{
Value = (T)info.GetValue("value", typeof(T));
}
/// <summary>Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with the data needed to serialize the target object.</summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to populate with data.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission.</exception>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("value", Value, typeof(T));
}
/// <summary>
/// Creates a new instance of the <see cref="DoubleTag"/> class with the specified <paramref name="value"/>.
/// </summary>
/// <param name="type">A constant describing the NBT type for this tag.</param>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="value">The value to assign to this tag.</param>
protected Tag(TagType type, string? name, T value) : base(type, name)
{
Value = value;
}
/// <inheritdoc cref="Tag.PrettyPrinted(StringBuilder,int,string)"/>
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{
for (var i = 0; i < level; i++)
buffer.Append(indent);
buffer.AppendLine(ToString());
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// <see langword="true" /> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IEquatable-1.Equals?view=netstandard-2.1">`IEquatable.Equals` on docs.microsoft.com</a></footer>
public bool Equals(Tag<T>? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return base.Equals(other) && EqualityComparer<T>.Default.Equals(Value, other.Value);
}
/// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns>
/// <see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.Equals?view=netstandard-2.1">`Object.Equals` on docs.microsoft.com</a></footer>
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((Tag<T>)obj);
}
/// <summary>Serves as the default hash function.</summary>
/// <returns>A hash code for the current object.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.GetHashCode?view=netstandard-2.1">`Object.GetHashCode` on docs.microsoft.com</a></footer>
public override int GetHashCode()
{
unchecked
{
// ReSharper disable NonReadonlyMemberInGetHashCode
return (base.GetHashCode() * 421) ^ EqualityComparer<T>.Default.GetHashCode(Value);
// ReSharper restore NonReadonlyMemberInGetHashCode
}
}
/// <summary>
/// Tests for equality of this object with another <see cref="Tag"/> instance.
/// </summary>
/// <param name="left">First value to compare.</param>
/// <param name="right">Second value to compare.</param>
/// <returns>Result of comparison.</returns>
public static bool operator ==(Tag<T> left, Tag<T> right) => Equals(left, right);
/// <summary>
/// Tests for inequality of this object with another <see cref="Tag"/> instance.
/// </summary>
/// <param name="left">First value to compare.</param>
/// <param name="right">Second value to compare.</param>
/// <returns>Result of comparison.</returns>
public static bool operator !=(Tag<T> left, Tag<T> right) => !Equals(left, right);
}

View File

@ -12,6 +12,7 @@ public enum TagType : byte
/// <summary>
/// Signifies the end of a <see cref="CompoundTag"/>.
/// </summary>
/// <remarks>Some implementation may also use as the child type for an empty list.</remarks>
End = 0x00,
/// <summary>