Implemented BufferedTagWriter class

This commit is contained in:
ForeverZer0 2021-08-25 03:12:32 -04:00
parent 2e15b58792
commit 7e30b7f898
4 changed files with 128 additions and 5 deletions

View File

@ -6,8 +6,8 @@ A CLS-compliant implementation of the Named Binary Tag (NBT) specifications (Jav
## Features ## 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. * **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 public member: Spend more time being productive and less time digging through documentation. * **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. * **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. * **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: * **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). The project is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
## Code of Conduct ## Code of Conduct
Everyone interacting in the SharpNBT projects 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). Everyone interacting in the SharpNBT projects 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.

View File

@ -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
{
/// <summary>
/// Provides a <see cref="TagWriter"/> object that writes to an internal buffer instead of a <see cref="Stream"/> 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.
/// </summary>
[PublicAPI]
public class BufferedTagWriter : TagWriter
{
private readonly MemoryStream buffer;
/// <summary>
/// Creates a new instance of the <see cref="BufferedTagWriter"/> class.
/// </summary>
/// <param name="compression">Indicates the compression algorithm used to compress the file.</param>
/// <param name="options">Bitwise flags to configure how data should be handled for compatibility between different specifications.</param>
/// <returns>A newly created <see cref="BufferedTagWriter"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when an invalid compression type is specified.</exception>
public static BufferedTagWriter Create(CompressionType compression, FormatOptions options)
{
// ReSharper disable once IntroduceOptionalParameters.Global
return Create(compression, options, 4096);
}
/// <summary>
/// Creates a new instance of the <see cref="BufferedTagWriter"/> class.
/// </summary>
/// <param name="compression">Indicates the compression algorithm used to compress the file.</param>
/// <param name="options">Bitwise flags to configure how data should be handled for compatibility between different specifications.</param>
/// <param name="capacity">The initial capacity of the buffer.</param>
/// <returns>A newly created <see cref="BufferedTagWriter"/> instance.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when an invalid compression type is specified.</exception>
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;
}
/// <summary>
/// Gets the number of bytes in the internal buffer.
/// </summary>
public long Length
{
get
{
BaseStream.Flush();
return buffer.Length;
}
}
/// <summary>
/// Gets the capacity of the internal buffer.
/// </summary>
/// <remarks>The capacity will expand automatically as-needed.</remarks>
public long Capacity => buffer.Capacity;
/// <summary>
/// Gets the internal buffer as an array of bytes containing the NBT data written so far.
/// </summary>
/// <returns>An array of bytes containing the NBT data.</returns>
[Pure]
public byte[] ToArray()
{
BaseStream.Flush();
return buffer.ToArray();
}
/// <summary>
/// Copies the internal buffer to the specified <paramref name="stream"/>;
/// </summary>
/// <param name="stream">A <see cref="Stream"/> instance to write to.</param>
public void CopyTo([NotNull] Stream stream)
{
BaseStream.Flush();
buffer.CopyTo(stream);
}
/// <summary>
/// Asynchronously copies the internal buffer to the specified <paramref name="stream"/>;
/// </summary>
/// <param name="stream">A <see cref="Stream"/> instance to write to.</param>
public async Task CopyToAsync([NotNull] Stream stream)
{
await BaseStream.FlushAsync();
await buffer.CopyToAsync(stream);
}
public static implicit operator ReadOnlySpan<byte>(BufferedTagWriter writer)
{
writer.BaseStream.Flush();
return writer.buffer.ToArray();
}
}
}

View File

@ -7,7 +7,6 @@ using JetBrains.Annotations;
namespace SharpNBT namespace SharpNBT
{ {
/// <summary> /// <summary>
/// Provides methods for reading NBT data from a stream. /// Provides methods for reading NBT data from a stream.
/// </summary> /// </summary>

View File

@ -215,6 +215,12 @@ namespace SharpNBT
BaseStream.WriteByte((byte) TagType.End); BaseStream.WriteByte((byte) TagType.End);
} }
/// <summary>
/// Convenience method to build and write a <see cref="TagBuilder"/> instance to the underlying stream.
/// </summary>
/// <param name="builder">A <see cref="TagBuilder"/> instance to write.</param>
public virtual void WriteBuilder([NotNull] TagBuilder builder) => WriteCompound(builder.Create());
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>