From 7e30b7f898a7785442487415c6edc0823c821b00 Mon Sep 17 00:00:00 2001 From: ForeverZer0 Date: Wed, 25 Aug 2021 03:12:32 -0400 Subject: [PATCH] Implemented BufferedTagWriter class --- README.md | 11 ++-- SharpNBT/BufferedTagWriter.cs | 115 ++++++++++++++++++++++++++++++++++ SharpNBT/TagReader.cs | 1 - SharpNBT/TagWriter.cs | 6 ++ 4 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 SharpNBT/BufferedTagWriter.cs diff --git a/README.md b/README.md index b83eee6..9e502a5 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ A CLS-compliant implementation of the Named Binary Tag (NBT) specifications (Jav ## Features -* **Java/Bedrock Support:** Supports all NBT protocols used by different versions of Minecraft, including: Java, Bedrock (file protocol), and Bedrock (network protocol), including full support for either GZip or ZLib compression. -* **Ease-of-use:** An intuitive API design, following the style and conventions of the .NET runtime, with full Intellisense for every public member: Spend more time being productive and less time digging through documentation. +* **Java/Bedrock Support:** Supports all NBT protocols used by different versions of Minecraft, including: Java, Bedrock (file protocol), and Bedrock (network protocol), including full support for either GZip/ZLib compression, big/little endian, and variable length integers with optional ZigZag encoding. +* **Ease-of-use:** An intuitive API design, following the style and conventions of the .NET runtime, with full Intellisense for every member: Spend more time being productive and less time digging through documentation. * **Performance:** Leverages the power of modern C# language features, including `Span` with `stackalloc`, `MemoryMarshal`, etc. This allows for a type-safe way to reinterpret raw buffers without pointers or making unnecessary copies of buffers, a common pitfall with serialization in type-safe languages. * **Concurrency:** Includes standard async/await concurrency patterns for reading and writing. * **Cross-platform and cross-language support:** Fully CLR compliant and build against .NET Standard 2.1, allowing support for any CLR language (i.e. C#, Visual Basic, F#, etc.) for the following runtime versions or greater: @@ -114,7 +114,10 @@ Pull requests are always welcome. The project is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). - ## Code of Conduct -Everyone interacting in the SharpNBT project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ForeverZer0/SharpNBT/blob/master/CODE_OF_CONDUCT.md). \ No newline at end of file +Everyone interacting in the SharpNBT project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ForeverZer0/SharpNBT/blob/master/CODE_OF_CONDUCT.md). + +## Special Thanks + +This project would not be possible without all the contributors to the https://wiki.vg/ and its maintainers, who have created an invaluable source of information for developers for everything related to the game of Minecraft. diff --git a/SharpNBT/BufferedTagWriter.cs b/SharpNBT/BufferedTagWriter.cs new file mode 100644 index 0000000..5f24104 --- /dev/null +++ b/SharpNBT/BufferedTagWriter.cs @@ -0,0 +1,115 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Threading.Tasks; +using JetBrains.Annotations; +using SharpNBT.ZLib; + +namespace SharpNBT +{ + /// + /// Provides a object that writes to an internal buffer instead of a object, which then can be retrieved as + /// an array of bytes or written directly to a stream. This is especially convenient when creating packets to be sent over a network, where the size of + /// the packet must be pre-determined before sending. + /// + [PublicAPI] + public class BufferedTagWriter : TagWriter + { + private readonly MemoryStream buffer; + + /// + /// Creates a new instance of the class. + /// + /// Indicates the compression algorithm used to compress the file. + /// Bitwise flags to configure how data should be handled for compatibility between different specifications. + /// A newly created instance. + /// Thrown when an invalid compression type is specified. + public static BufferedTagWriter Create(CompressionType compression, FormatOptions options) + { + // ReSharper disable once IntroduceOptionalParameters.Global + return Create(compression, options, 4096); + } + + /// + /// Creates a new instance of the class. + /// + /// Indicates the compression algorithm used to compress the file. + /// Bitwise flags to configure how data should be handled for compatibility between different specifications. + /// The initial capacity of the buffer. + /// A newly created instance. + /// Thrown when an invalid compression type is specified. + public static BufferedTagWriter Create(CompressionType compression, FormatOptions options, int capacity) + { + var buffer = new MemoryStream(capacity); + Stream stream = compression switch + { + CompressionType.None => buffer, + CompressionType.GZip => new GZipStream(buffer, CompressionMode.Compress, false), + CompressionType.ZLib => new ZLibStream(buffer, CompressionMode.Compress), + _ => throw new ArgumentOutOfRangeException(nameof(compression), compression, null) + }; + + return new BufferedTagWriter(stream, buffer, options); + } + + private BufferedTagWriter([NotNull] Stream stream, MemoryStream buffer, FormatOptions options) : base(stream, options, false) + { + this.buffer = buffer; + } + + /// + /// Gets the number of bytes in the internal buffer. + /// + public long Length + { + get + { + BaseStream.Flush(); + return buffer.Length; + } + } + + /// + /// Gets the capacity of the internal buffer. + /// + /// The capacity will expand automatically as-needed. + public long Capacity => buffer.Capacity; + + /// + /// Gets the internal buffer as an array of bytes containing the NBT data written so far. + /// + /// An array of bytes containing the NBT data. + [Pure] + public byte[] ToArray() + { + BaseStream.Flush(); + return buffer.ToArray(); + } + + /// + /// Copies the internal buffer to the specified ; + /// + /// A instance to write to. + public void CopyTo([NotNull] Stream stream) + { + BaseStream.Flush(); + buffer.CopyTo(stream); + } + + /// + /// Asynchronously copies the internal buffer to the specified ; + /// + /// A instance to write to. + public async Task CopyToAsync([NotNull] Stream stream) + { + await BaseStream.FlushAsync(); + await buffer.CopyToAsync(stream); + } + + public static implicit operator ReadOnlySpan(BufferedTagWriter writer) + { + writer.BaseStream.Flush(); + return writer.buffer.ToArray(); + } + } +} \ No newline at end of file diff --git a/SharpNBT/TagReader.cs b/SharpNBT/TagReader.cs index 3c54912..016d44e 100644 --- a/SharpNBT/TagReader.cs +++ b/SharpNBT/TagReader.cs @@ -7,7 +7,6 @@ using JetBrains.Annotations; namespace SharpNBT { - /// /// Provides methods for reading NBT data from a stream. /// diff --git a/SharpNBT/TagWriter.cs b/SharpNBT/TagWriter.cs index 9568af6..36c2b63 100644 --- a/SharpNBT/TagWriter.cs +++ b/SharpNBT/TagWriter.cs @@ -215,6 +215,12 @@ namespace SharpNBT BaseStream.WriteByte((byte) TagType.End); } + /// + /// Convenience method to build and write a instance to the underlying stream. + /// + /// A instance to write. + public virtual void WriteBuilder([NotNull] TagBuilder builder) => WriteCompound(builder.Create()); + /// /// ///