diff options
author | vnugent <public@vaughnnugent.com> | 2023-05-26 21:44:22 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-05-26 21:44:22 -0400 |
commit | 858773acf273567bcb7eb5b18c45c9d8e6a97349 (patch) | |
tree | 8fc955a54ff7c686f865fc6c62ec523a9fc229c4 /lib | |
parent | fc6cb7a1e66aa96ab468b8ad7ff12329d992e35f (diff) |
Minior refactor
Diffstat (limited to 'lib')
19 files changed, 349 insertions, 136 deletions
diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs index ef313ca..c5409a8 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs @@ -310,7 +310,7 @@ namespace VNLib.Hashing.IdentityUtility ///<inheritdoc/> ///<exception cref="ObjectDisposedException"></exception> - public virtual ERRNO Compile(in Span<char> buffer) + public virtual ERRNO Compile(Span<char> buffer) { ForwardOnlyWriter<char> writer = new(buffer); Compile(ref writer); diff --git a/lib/Net.Http/src/Core/HttpCookie.cs b/lib/Net.Http/src/Core/HttpCookie.cs index c48ad00..e0e5406 100644 --- a/lib/Net.Http/src/Core/HttpCookie.cs +++ b/lib/Net.Http/src/Core/HttpCookie.cs @@ -103,7 +103,7 @@ namespace VNLib.Net.Http.Core writer.Append("; Secure"); } } - public ERRNO Compile(in Span<char> buffer) + public ERRNO Compile(Span<char> buffer) { ForwardOnlyWriter<char> writer = new(buffer); Compile(ref writer); diff --git a/lib/Net.Http/src/Core/Request/HttpRequest.cs b/lib/Net.Http/src/Core/Request/HttpRequest.cs index f638ac9..b036680 100644 --- a/lib/Net.Http/src/Core/Request/HttpRequest.cs +++ b/lib/Net.Http/src/Core/Request/HttpRequest.cs @@ -270,7 +270,7 @@ namespace VNLib.Net.Http.Core writer.Append("\r\n"); } - public ERRNO Compile(in Span<char> buffer) + public ERRNO Compile(Span<char> buffer) { ForwardOnlyWriter<char> writer = new(buffer); Compile(ref writer); diff --git a/lib/Net.Http/src/Core/Response/ChunkedStream.cs b/lib/Net.Http/src/Core/Response/ChunkedStream.cs index 1b4f7de..3aa4330 100644 --- a/lib/Net.Http/src/Core/Response/ChunkedStream.cs +++ b/lib/Net.Http/src/Core/Response/ChunkedStream.cs @@ -93,7 +93,7 @@ namespace VNLib.Net.Http.Core } //Init reader - ForwardOnlyReader<byte> reader = new(in chunk); + ForwardOnlyReader<byte> reader = new(chunk); try { do @@ -147,7 +147,7 @@ namespace VNLib.Net.Http.Core try { //Init reader - ForwardOnlyMemoryReader<byte> reader = new(in chunk); + ForwardOnlyMemoryReader<byte> reader = new(chunk); do { diff --git a/lib/Net.Http/src/Core/Response/HttpResponse.cs b/lib/Net.Http/src/Core/Response/HttpResponse.cs index 952311d..6d974ed 100644 --- a/lib/Net.Http/src/Core/Response/HttpResponse.cs +++ b/lib/Net.Http/src/Core/Response/HttpResponse.cs @@ -382,7 +382,7 @@ namespace VNLib.Net.Http.Core writer.Append(HttpHelpers.CRLF); } - public ERRNO Compile(in Span<char> buffer) + public ERRNO Compile(Span<char> buffer) { ForwardOnlyWriter<char> writer = new(buffer); Compile(ref writer); diff --git a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs index bffa1ca..c4fb493 100644 --- a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs +++ b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs @@ -126,9 +126,11 @@ namespace VNLib.Net.Messaging.FBM.Client ///<inheritdoc/> [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteHeader(HeaderCommand header, ReadOnlySpan<char> value) => WriteHeader((byte)header, value); + ///<inheritdoc/> [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteHeader(byte header, ReadOnlySpan<char> value) => Helpers.WriteHeader(Buffer.RequestBuffer, header, value, Helpers.DefaultEncoding); + ///<inheritdoc/> public void WriteBody(ReadOnlySpan<byte> body, ContentType contentType = ContentType.Binary) { @@ -244,7 +246,7 @@ namespace VNLib.Net.Messaging.FBM.Client using UnsafeMemoryHandle<char> buffer = MemoryUtil.UnsafeAlloc<char>(charSize + 128); - ERRNO count = Compile(buffer.Span); + ERRNO count = Compile(buffer); return buffer.AsSpan(0, count).ToString(); } @@ -258,7 +260,7 @@ namespace VNLib.Net.Messaging.FBM.Client Helpers.DefaultEncoding.GetChars(requestData.Span, ref writer); } ///<inheritdoc/> - public ERRNO Compile(in Span<char> buffer) + public ERRNO Compile(Span<char> buffer) { ForwardOnlyWriter<char> writer = new(buffer); Compile(ref writer); diff --git a/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs b/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs index df366e3..b62412f 100644 --- a/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs +++ b/lib/Utils/src/IO/ArrayPoolStreamBuffer.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -27,32 +27,46 @@ using System.Buffers; namespace VNLib.Utils.IO { + internal class ArrayPoolStreamBuffer<T> : ISlindingWindowBuffer<T> { + /// <summary> + /// The shared <see cref="IStreamBufferFactory{T}"/> instance to allocate buffers + /// from + /// </summary> + public static IStreamBufferFactory<T> Shared { get; } = new DefaultFactory(); + private readonly ArrayPool<T> _pool; private T[] _buffer; - public ArrayPoolStreamBuffer(ArrayPool<T> pool, int bufferSize) + /// <summary> + /// Creates a new <see cref="ArrayPoolStreamBuffer{T}"/> from the + /// given array instance and <see cref="ArrayPool{T}"/> it came from. + /// </summary> + /// <param name="array">The rented array to use</param> + /// <param name="pool">The pool to return the array to when completed</param> + public ArrayPoolStreamBuffer(T[] array, ArrayPool<T> pool) { _pool = pool; - _buffer = _pool.Rent(bufferSize); + _buffer = array; } + ///<inheritdoc/> public int WindowStartPos { get; set; } + + ///<inheritdoc/> public int WindowEndPos { get; set; } - + + ///<inheritdoc/> public Memory<T> Buffer => _buffer.AsMemory(); - public void Advance(int count) - { - WindowEndPos += count; - } + ///<inheritdoc/> + public void Advance(int count) => WindowEndPos += count; - public void AdvanceStart(int count) - { - WindowStartPos += count; - } + ///<inheritdoc/> + public void AdvanceStart(int count) => WindowStartPos += count; + ///<inheritdoc/> public void Close() { //Return buffer to pool @@ -60,11 +74,25 @@ namespace VNLib.Utils.IO _buffer = null; } + ///<inheritdoc/> public void Reset() { //Reset window positions WindowStartPos = 0; WindowEndPos = 0; } + + private sealed class DefaultFactory : IStreamBufferFactory<T> + { + ///<inheritdoc/> + public ISlindingWindowBuffer<T> CreateBuffer(int bufferSize) + { + //rent buffer + T[] array = ArrayPool<T>.Shared.Rent(bufferSize); + + //return wrapper + return new ArrayPoolStreamBuffer<T>(array, ArrayPool<T>.Shared); + } + } } }
\ No newline at end of file diff --git a/lib/Utils/src/IO/FileOperations.cs b/lib/Utils/src/IO/FileOperations.cs index e040da4..a8cd258 100644 --- a/lib/Utils/src/IO/FileOperations.cs +++ b/lib/Utils/src/IO/FileOperations.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -27,7 +27,8 @@ using System.IO; using System.Runtime.InteropServices; namespace VNLib.Utils.IO -{ +{ + /// <summary> /// Contains cross-platform optimized filesystem operations. /// </summary> @@ -38,13 +39,15 @@ namespace VNLib.Utils.IO [DllImport("Shlwapi", SetLastError = true, CharSet = CharSet.Auto)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [return:MarshalAs(UnmanagedType.Bool)] - private static unsafe extern bool PathFileExists(char* path); + private static unsafe extern bool PathFileExists([MarshalAs(UnmanagedType.LPWStr)] string path); + [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Auto)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [return:MarshalAs(UnmanagedType.I4)] - private static unsafe extern int GetFileAttributes(char* path); + private static unsafe extern int GetFileAttributes([MarshalAs(UnmanagedType.LPWStr)] string path); static readonly bool IsWindows = OperatingSystem.IsWindows(); + /// <summary> /// Determines if a file exists. If application is current running in the Windows operating system, Shlwapi.PathFileExists is invoked, /// otherwise <see cref="File.Exists(string?)"/> is invoked @@ -58,15 +61,9 @@ namespace VNLib.Utils.IO { return File.Exists(filePath); } - unsafe - { - //Get a char pointer to the file path - fixed (char* path = filePath) - { - //Invoke the winap file function - return PathFileExists(path); - } - } + + //Invoke the winapi file function + return PathFileExists(filePath); } /// <summary> @@ -84,22 +81,18 @@ namespace VNLib.Utils.IO { return File.GetAttributes(filePath); } - unsafe + + //Invoke the winapi file function and cast the returned int value to file attributes + int attr = GetFileAttributes(filePath); + + //Check for error + if (attr == INVALID_FILE_ATTRIBUTES) { - //Get a char pointer to the file path - fixed (char* path = filePath) - { - //Invoke the winap file function and cast the returned int value to file attributes - int attr = GetFileAttributes(path); - //Check for error - if (attr == INVALID_FILE_ATTRIBUTES) - { - throw new FileNotFoundException("The requested file was not found", filePath); - } - //Cast to file attributes and return - return (FileAttributes)attr; - } + throw new FileNotFoundException("The requested file was not found", filePath); } + + //Cast to file attributes and return + return (FileAttributes)attr; } } }
\ No newline at end of file diff --git a/lib/Utils/src/IO/IStreamBufferFactory.cs b/lib/Utils/src/IO/IStreamBufferFactory.cs new file mode 100644 index 0000000..f8cfe2d --- /dev/null +++ b/lib/Utils/src/IO/IStreamBufferFactory.cs @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: VnStreamReader.cs +* +* VnStreamReader.cs is part of VNLib.Utils which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Utils is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Utils is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. +*/ + +namespace VNLib.Utils.IO +{ + /// <summary> + /// An interface that allows creating a <see cref="ISlindingWindowBuffer{T}"/> of the specified type + /// for stream reading/writing + /// </summary> + /// <typeparam name="T">The buffer element type</typeparam> + public interface IStreamBufferFactory<T> + { + /// <summary> + /// Creates a new <see cref="ISlindingWindowBuffer{T}"/> of the specified size + /// </summary> + /// <param name="bufferSize">The minimum size of the buffer to allocate</param> + /// <returns>The buffer instance</returns> + ISlindingWindowBuffer<T> CreateBuffer(int bufferSize); + } +}
\ No newline at end of file diff --git a/lib/Utils/src/IO/SlidingWindowBufferExtensions.cs b/lib/Utils/src/IO/SlidingWindowBufferExtensions.cs index 0509061..c8c62d3 100644 --- a/lib/Utils/src/IO/SlidingWindowBufferExtensions.cs +++ b/lib/Utils/src/IO/SlidingWindowBufferExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -26,10 +26,10 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using System.Runtime.CompilerServices; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; -using System.Runtime.CompilerServices; namespace VNLib.Utils.IO { @@ -49,7 +49,7 @@ namespace VNLib.Utils.IO //Nothing to compact if the starting data pointer is at the beining of the window if (sBuf.WindowStartPos > 0) { - //Get span over engire buffer + //Get span over entire buffer Span<T> buffer = sBuf.Buffer.Span; //Get data within window Span<T> usedData = sBuf.Accumulated; @@ -79,6 +79,7 @@ namespace VNLib.Utils.IO //Advance by 1 sBuf.Advance(1); } + /// <summary> /// Appends the specified data to the end of the buffer /// </summary> @@ -92,6 +93,7 @@ namespace VNLib.Utils.IO val.CopyTo(sBuf.Remaining); sBuf.Advance(val.Length); } + /// <summary> /// Formats and appends a value type to the accumulator with proper endianess /// </summary> @@ -165,7 +167,7 @@ namespace VNLib.Utils.IO /// <param name="sBuf"></param> /// <param name="buffer">The output buffer to write data to</param> /// <returns>The number of elements written to the buffer</returns> - public static ERRNO Read<T>(this ISlindingWindowBuffer<T> sBuf, in Span<T> buffer) + public static ERRNO Read<T>(this ISlindingWindowBuffer<T> sBuf, Span<T> buffer) { //Calculate the amount of data to copy int dataToCopy = Math.Min(buffer.Length, sBuf.AccumulatedSize); @@ -194,6 +196,7 @@ namespace VNLib.Utils.IO //Update the end of the buffer window to the end of the read data accumulator.Advance(read); } + /// <summary> /// Fills the remaining window space of the current accumulator with /// data from the specified stream. diff --git a/lib/Utils/src/IO/VnStreamReader.cs b/lib/Utils/src/IO/VnStreamReader.cs index 70b9734..a03a1de 100644 --- a/lib/Utils/src/IO/VnStreamReader.cs +++ b/lib/Utils/src/IO/VnStreamReader.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -33,6 +33,7 @@ using VNLib.Utils.Extensions; namespace VNLib.Utils.IO { + /// <summary> /// Binary based buffered text reader, optimized for reading network streams /// </summary> @@ -41,8 +42,10 @@ namespace VNLib.Utils.IO private bool disposedValue; private readonly ISlindingWindowBuffer<byte> _buffer; + ///<inheritdoc/> public virtual Stream BaseStream { get; } + ///<inheritdoc/> public Encoding Encoding { get; } @@ -50,33 +53,56 @@ namespace VNLib.Utils.IO /// Number of available bytes of buffered data within the current buffer window /// </summary> public int Available => _buffer.AccumulatedSize; + /// <summary> /// Gets or sets the line termination used to deliminate a line of data /// </summary> public ReadOnlyMemory<byte> LineTermination { get; set; } - Span<byte> IVnTextReader.BufferedDataWindow => _buffer.Accumulated; + + ///<inheritdoc/> + public Span<byte> BufferedDataWindow => _buffer.Accumulated; /// <summary> - /// Creates a new <see cref="TextReader"/> that reads encoded data from the base. - /// Internal buffers will be alloced from <see cref="ArrayPool{T}.Shared"/> + /// Creates a new <see cref="TextReader"/> that reads encoded data from the base stream + /// and allocates a new buffer of the specified size from the shared <see cref="ArrayPool{T}"/> /// </summary> /// <param name="baseStream">The underlying stream to read data from</param> - /// <param name="enc">The <see cref="Encoding"/> to use when reading from the stream</param> + /// <param name="encoding">The <see cref="Encoding"/> to use when reading from the stream</param> /// <param name="bufferSize">The size of the internal binary buffer</param> - public VnStreamReader(Stream baseStream, Encoding enc, int bufferSize) + /// <exception cref="ArgumentNullException"></exception> + public VnStreamReader(Stream baseStream, Encoding encoding, int bufferSize) + : this(baseStream, encoding, bufferSize, ArrayPoolStreamBuffer<byte>.Shared) { - BaseStream = baseStream; - Encoding = enc; - //Init a new buffer - _buffer = InitializeBuffer(bufferSize); } /// <summary> - /// Invoked by the constuctor method to allocte the internal buffer with the specified buffer size. + /// Creates a new <see cref="TextReader"/> that reads encoded data from the base stream + /// and allocates a new buffer of the specified size from the supplied buffer factory. /// </summary> - /// <param name="bufferSize">The requested size of the buffer to alloc</param> - /// <remarks>By default requests the buffer from the <see cref="ArrayPool{T}.Shared"/> instance</remarks> - protected virtual ISlindingWindowBuffer<byte> InitializeBuffer(int bufferSize) => new ArrayPoolStreamBuffer<byte>(ArrayPool<byte>.Shared, bufferSize); + /// <param name="baseStream">The underlying stream to read data from</param> + /// <param name="encoding">The <see cref="Encoding"/> to use when reading from the stream</param> + /// <param name="bufferSize">The size of the internal binary buffer</param> + /// <param name="bufferFactory">The buffer factory to create the buffer from</param> + /// <exception cref="ArgumentNullException"></exception> + public VnStreamReader(Stream baseStream, Encoding encoding, int bufferSize, IStreamBufferFactory<byte> bufferFactory) + :this(baseStream, encoding, bufferFactory?.CreateBuffer(bufferSize)!) + { + } + + /// <summary> + /// Creates a new <see cref="TextReader"/> that reads encoded data from the base stream + /// and uses the specified buffer. + /// </summary> + /// <param name="baseStream">The underlying stream to read data from</param> + /// <param name="encoding">The <see cref="Encoding"/> to use when reading from the stream</param> + /// <param name="buffer">The internal <see cref="ISlindingWindowBuffer{T}"/> to use</param> + /// <exception cref="ArgumentNullException"></exception> + public VnStreamReader(Stream baseStream, Encoding encoding, ISlindingWindowBuffer<byte> buffer) + { + BaseStream = baseStream ?? throw new ArgumentNullException(nameof(buffer)); + Encoding = encoding ?? throw new ArgumentNullException(nameof(encoding)); + _buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); + } ///<inheritdoc/> public override async Task<string?> ReadLineAsync() @@ -86,42 +112,53 @@ namespace VNLib.Utils.IO { //Get current buffer window Memory<byte> buffered = _buffer.AccumulatedBuffer; + //search for line termination in current buffer int term = buffered.IndexOf(LineTermination); + //Termination found in buffer window if (term > -1) { //Capture the line from the begining of the window to the termination Memory<byte> line = buffered[..term]; + //Shift the window to the end of the line (excluding the termination) _buffer.AdvanceStart(term + LineTermination.Length); + //Decode the line to a string return Encoding.GetString(line.Span); } //Termination not found } + //Compact the buffer window and see if space is avialble to buffer more data if (_buffer.CompactBufferWindow()) { //There is room, so buffer more data await _buffer.AccumulateDataAsync(BaseStream, CancellationToken.None); + //Check again to see if more data is buffered if (Available <= 0) { //No string found return null; } + //Get current buffer window Memory<byte> buffered = _buffer.AccumulatedBuffer; + //search for line termination in current buffer int term = buffered.IndexOf(LineTermination); + //Termination found in buffer window if (term > -1) { //Capture the line from the begining of the window to the termination Memory<byte> line = buffered[..term]; + //Shift the window to the end of the line (excluding the termination) _buffer.AdvanceStart(term + LineTermination.Length); + //Decode the line to a string return Encoding.GetString(line.Span); } @@ -136,6 +173,7 @@ namespace VNLib.Utils.IO ///<inheritdoc/> public override int Read(char[] buffer, int index, int count) => Read(buffer.AsSpan(index, count)); + ///<inheritdoc/> public override int Read(Span<char> buffer) { @@ -143,17 +181,23 @@ namespace VNLib.Utils.IO { return 0; } + //Get current buffer window Span<byte> buffered = _buffer.Accumulated; + //Convert all avialable data int encoded = Encoding.GetChars(buffered, buffer); + //Shift buffer window to the end of the converted data _buffer.AdvanceStart(encoded); + //return the number of chars written return Encoding.GetCharCount(buffered); } + ///<inheritdoc/> public override void Close() => _buffer.Close(); + ///<inheritdoc/> protected override void Dispose(bool disposing) { @@ -173,8 +217,14 @@ namespace VNLib.Utils.IO _buffer.Reset(); } - void IVnTextReader.Advance(int count) => _buffer.AdvanceStart(count); - void IVnTextReader.FillBuffer() => _buffer.AccumulateData(BaseStream); - ERRNO IVnTextReader.CompactBufferWindow() => _buffer.CompactBufferWindow(); + + ///<inheritdoc/> + public void Advance(int count) => _buffer.AdvanceStart(count); + + ///<inheritdoc/> + public void FillBuffer() => _buffer.AccumulateData(BaseStream); + + ///<inheritdoc/> + public ERRNO CompactBufferWindow() => _buffer.CompactBufferWindow(); } }
\ No newline at end of file diff --git a/lib/Utils/src/IO/VnStreamWriter.cs b/lib/Utils/src/IO/VnStreamWriter.cs index f875932..03b0a6e 100644 --- a/lib/Utils/src/IO/VnStreamWriter.cs +++ b/lib/Utils/src/IO/VnStreamWriter.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -51,6 +51,7 @@ namespace VNLib.Utils.IO /// Gets the underlying stream that interfaces with the backing store /// </summary> public virtual Stream BaseStream { get; } + ///<inheritdoc/> public override Encoding Encoding { get; } @@ -58,36 +59,60 @@ namespace VNLib.Utils.IO /// Line termination to use when writing lines to the output /// </summary> public ReadOnlyMemory<byte> LineTermination { get; set; } + ///<inheritdoc/> public override string NewLine { get => Encoding.GetString(LineTermination.Span); set => LineTermination = Encoding.GetBytes(value); } - + + + /// <summary> + /// Creates a new <see cref="VnStreamWriter"/> that writes encoded data to the base stream + /// and allocates a new buffer of the specified size from the shared <see cref="ArrayPool{T}"/> + /// </summary> + /// <param name="baseStream">The underlying stream to write data to</param> + /// <param name="encoding">The <see cref="Encoding"/> to use when writing to the stream</param> + /// <param name="bufferSize">The size of the internal binary buffer</param> + /// <exception cref="ArgumentNullException"></exception> + public VnStreamWriter(Stream baseStream, Encoding encoding, int bufferSize) + : this(baseStream, encoding, bufferSize, ArrayPoolStreamBuffer<byte>.Shared) + { + } + /// <summary> - /// Creates a new <see cref="VnStreamWriter"/> that writes formatted data - /// to the specified base stream + /// Creates a new <see cref="VnStreamWriter"/> that writes encoded data to the base stream + /// and allocates a new buffer of the specified size from the supplied buffer factory. /// </summary> - /// <param name="baseStream">The stream to write data to</param> - /// <param name="encoding">The <see cref="Encoding"/> to use when writing data</param> - /// <param name="bufferSize">The size of the internal buffer used to buffer binary data before writing to the base stream</param> - public VnStreamWriter(Stream baseStream, Encoding encoding, int bufferSize = 1024) + /// <param name="baseStream">The underlying stream to write data to</param> + /// <param name="encoding">The <see cref="Encoding"/> to use when writing to the stream</param> + /// <param name="bufferSize">The size of the internal binary buffer</param> + /// <param name="bufferFactory">The buffer factory to create the buffer from</param> + /// <exception cref="ArgumentNullException"></exception> + public VnStreamWriter(Stream baseStream, Encoding encoding, int bufferSize, IStreamBufferFactory<byte> bufferFactory) + : this(baseStream, encoding, bufferFactory?.CreateBuffer(bufferSize)!) { - //Store base stream - BaseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); + } + + /// <summary> + /// Creates a new <see cref="VnStreamWriter"/> that writes encoded data to the base stream + /// and uses the specified buffer. + /// </summary> + /// <param name="baseStream">The underlying stream to write data to</param> + /// <param name="encoding">The <see cref="Encoding"/> to use when writing to the stream</param> + /// <param name="buffer">The internal <see cref="ISlindingWindowBuffer{T}"/> to use</param> + /// <exception cref="ArgumentNullException"></exception> + public VnStreamWriter(Stream baseStream, Encoding encoding, ISlindingWindowBuffer<byte> buffer) + { + BaseStream = baseStream ?? throw new ArgumentNullException(nameof(buffer)); Encoding = encoding ?? throw new ArgumentNullException(nameof(encoding)); + _buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); + //Get an encoder Enc = encoding.GetEncoder(); - _buffer = InitializeBuffer(bufferSize); } - /// <summary> - /// Invoked by the constuctor method to allocte the internal buffer with the specified buffer size. - /// </summary> - /// <param name="bufferSize">The requested size of the buffer to alloc</param> - /// <remarks>By default requests the buffer from the <see cref="MemoryPool{T}.Shared"/> instance</remarks> - protected virtual ISlindingWindowBuffer<byte> InitializeBuffer(int bufferSize) => new ArrayPoolStreamBuffer<byte>(ArrayPool<byte>.Shared, bufferSize); ///<inheritdoc/> public void Write(byte value) { @@ -97,19 +122,24 @@ namespace VNLib.Utils.IO //There is not enough room to store the single byte Flush(); } + //Store at the end of the window _buffer.Append(value); } + ///<inheritdoc/> public override void Write(char value) { ReadOnlySpan<char> tbuf = MemoryMarshal.CreateSpan(ref value, 0x01); Write(tbuf); } + ///<inheritdoc/> public override void Write(object? value) => Write(value?.ToString()); + ///<inheritdoc/> public override void Write(string? value) => Write(value.AsSpan()); + ///<inheritdoc/> public override void Write(ReadOnlySpan<char> buffer) { @@ -123,8 +153,10 @@ namespace VNLib.Utils.IO { //Get an available buffer window to store characters in and convert the characters to binary Enc.Convert(reader.Window, _buffer.Remaining, true, out int charsUsed, out int bytesUsed, out completed); + //Update byte position _buffer.Advance(bytesUsed); + //Update char position reader.Advance(charsUsed); @@ -136,31 +168,40 @@ namespace VNLib.Utils.IO } } while (!completed); + //Reset the encoder Enc.Reset(); } + ///<inheritdoc/> public override async Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) { Check(); + + ForwardOnlyMemoryReader<char> reader = new(buffer); + //Create a variable for a character buffer window bool completed; - ForwardOnlyMemoryReader<char> reader = new(buffer); do { //Get an available buffer window to store characters in and convert the characters to binary Enc.Convert(reader.Window.Span, _buffer.Remaining, true, out int charsUsed, out int bytesUsed, out completed); + //Update byte position _buffer.Advance(bytesUsed); + //Update char position reader.Advance(charsUsed); + //Converting did not complete because the buffer was too small if (!completed || reader.WindowSize == 0) { //Flush the buffer and continue await FlushWriterAsync(cancellationToken); } + } while (!completed); + //Reset the encoder Enc.Reset(); } @@ -169,18 +210,23 @@ namespace VNLib.Utils.IO public override void WriteLine() { Check(); + //See if there is room in the binary buffer if (_buffer.RemainingSize < LineTermination.Length) { //There is not enough room to store the termination, so we need to flush the buffer Flush(); } + _buffer.Append(LineTermination.Span); } + ///<inheritdoc/> public override void WriteLine(object? value) => WriteLine(value?.ToString()); + ///<inheritdoc/> public override void WriteLine(string? value) => WriteLine(value.AsSpan()); + ///<inheritdoc/> public override void WriteLine(ReadOnlySpan<char> buffer) { @@ -204,6 +250,7 @@ namespace VNLib.Utils.IO _buffer.Reset(); } } + /// <summary> /// Asynchronously flushes the internal buffers to the <see cref="BaseStream"/>, and resets the internal buffer state /// </summary> @@ -212,6 +259,7 @@ namespace VNLib.Utils.IO public async ValueTask FlushWriterAsync(CancellationToken cancellationToken = default) { Check(); + if (_buffer.AccumulatedSize > 0) { //Flush current window to the stream @@ -232,6 +280,7 @@ namespace VNLib.Utils.IO _buffer.Reset(); Enc.Reset(); } + ///<inheritdoc/> public override void Close() { @@ -252,6 +301,7 @@ namespace VNLib.Utils.IO closed = true; } } + ///<inheritdoc/> protected override void Dispose(bool disposing) { @@ -267,6 +317,7 @@ namespace VNLib.Utils.IO throw new ObjectDisposedException("The stream is closed"); } } + ///<inheritdoc/> public override async ValueTask DisposeAsync() { diff --git a/lib/Utils/src/IO/WriteOnlyBufferedStream.cs b/lib/Utils/src/IO/WriteOnlyBufferedStream.cs index 5e7faa1..e6e138e 100644 --- a/lib/Utils/src/IO/WriteOnlyBufferedStream.cs +++ b/lib/Utils/src/IO/WriteOnlyBufferedStream.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -24,7 +24,6 @@ using System; using System.IO; -using System.Buffers; using System.Threading; using System.Threading.Tasks; @@ -46,28 +45,45 @@ namespace VNLib.Utils.IO public Stream BaseStream { get; init; } /// <summary> - /// Initalizes a new <see cref="WriteOnlyBufferedStream"/> using the - /// specified backing stream, using the specified buffer size, and - /// optionally leaves the stream open + /// Initalizes a new <see cref="WriteOnlyBufferedStream"/> using the specified backing + /// stream, allocating a pooled buffer of the specified size, and optionally leaves the stream open. /// </summary> /// <param name="baseStream">The backing stream to write buffered data to</param> /// <param name="bufferSize">The size of the internal buffer</param> /// <param name="leaveOpen">A value indicating of the stream should be left open when the buffered stream is closed</param> + /// <exception cref="ArgumentNullException"></exception> public WriteOnlyBufferedStream(Stream baseStream, int bufferSize, bool leaveOpen = false) + : this(baseStream, bufferSize, ArrayPoolStreamBuffer<byte>.Shared, leaveOpen) + { + } + + /// <summary> + /// Creates a new <see cref="WriteOnlyBufferedStream"/> that writes encoded data to the base stream + /// and allocates a new buffer of the specified size from the supplied buffer factory. + /// </summary> + /// <param name="baseStream">The underlying stream to write data to</param> + /// <param name="bufferSize">The size of the internal binary buffer</param> + /// <param name="bufferFactory">The buffer factory to create the buffer from</param> + /// <param name="leaveOpen">A value indicating of the stream should be left open when the buffered stream is closed</param> + /// <exception cref="ArgumentNullException"></exception> + public WriteOnlyBufferedStream(Stream baseStream, int bufferSize, IStreamBufferFactory<byte> bufferFactory, bool leaveOpen = false) + : this(baseStream, bufferFactory?.CreateBuffer(bufferSize)!, leaveOpen) { - BaseStream = baseStream; - //Create buffer - _buffer = InitializeBuffer(bufferSize); - LeaveOpen = leaveOpen; } + /// <summary> - /// Invoked by the constuctor method to allocte the internal buffer with the specified buffer size. + /// Creates a new <see cref="WriteOnlyBufferedStream"/> that writes encoded data to the base stream + /// and uses the specified buffer. /// </summary> - /// <param name="bufferSize">The requested size of the buffer to alloc</param> - /// <remarks>By default requests the buffer from the <see cref="ArrayPool{T}.Shared"/> instance</remarks> - protected virtual ISlindingWindowBuffer<byte> InitializeBuffer(int bufferSize) + /// <param name="baseStream">The underlying stream to write data to</param> + /// <param name="buffer">The internal <see cref="ISlindingWindowBuffer{T}"/> to use</param> + /// <param name="leaveOpen">A value indicating of the stream should be left open when the buffered stream is closed</param> + /// <exception cref="ArgumentNullException"></exception> + public WriteOnlyBufferedStream(Stream baseStream, ISlindingWindowBuffer<byte> buffer, bool leaveOpen = false) { - return new ArrayPoolStreamBuffer<byte>(ArrayPool<byte>.Shared, bufferSize); + BaseStream = baseStream ?? throw new ArgumentNullException(nameof(buffer)); + _buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); + LeaveOpen = leaveOpen; } ///<inheritdoc/> @@ -89,6 +105,7 @@ namespace VNLib.Utils.IO _buffer.Close(); } } + ///<inheritdoc/> public override async ValueTask DisposeAsync() { @@ -115,6 +132,7 @@ namespace VNLib.Utils.IO ///<inheritdoc/> public override void Flush() => WriteBuffer(); + ///<inheritdoc/> public override Task FlushAsync(CancellationToken cancellationToken) => WriteBufferAsync(cancellationToken).AsTask(); @@ -138,6 +156,7 @@ namespace VNLib.Utils.IO _buffer.Reset(); } } + ///<inheritdoc/> public override void Write(byte[] buffer, int offset, int count) => Write(buffer.AsSpan(offset, count)); @@ -182,6 +201,7 @@ namespace VNLib.Utils.IO { //Buffer is full and needs to be flushed await WriteBufferAsync(cancellationToken); + //Advance reader and continue to buffer reader.Advance(buffered); continue; @@ -197,23 +217,28 @@ namespace VNLib.Utils.IO /// Always false /// </summary> public override bool CanRead => false; + /// <summary> /// Always returns false /// </summary> public override bool CanSeek => false; + /// <summary> /// Always true /// </summary> public override bool CanWrite => true; + /// <summary> /// Returns the size of the underlying buffer /// </summary> public override long Length => _buffer.AccumulatedSize; + /// <summary> /// Always throws <see cref="NotSupportedException"/> /// </summary> /// <exception cref="NotSupportedException"></exception> public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + /// <summary> /// Always throws <see cref="NotSupportedException"/> /// </summary> @@ -242,11 +267,19 @@ namespace VNLib.Utils.IO throw new NotSupportedException(); } + /// <summary> + /// Always throws <see cref="NotSupportedException"/> + /// </summary> + /// <exception cref="NotSupportedException"></exception> public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { throw new NotImplementedException(); } + /// <summary> + /// Always throws <see cref="NotSupportedException"/> + /// </summary> + /// <exception cref="NotSupportedException"></exception> public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs b/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs index 39c9594..53d3d77 100644 --- a/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs +++ b/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -31,7 +31,7 @@ namespace VNLib.Utils.Memory /// reader for a memory segment /// </summary> /// <typeparam name="T">The element type</typeparam> - public struct ForwardOnlyMemoryReader<T> + public record struct ForwardOnlyMemoryReader<T> { private readonly ReadOnlyMemory<T> _segment; private readonly int _size; @@ -43,7 +43,7 @@ namespace VNLib.Utils.Memory /// of the specified type using the specified internal buffer /// </summary> /// <param name="buffer">The buffer to read from</param> - public ForwardOnlyMemoryReader(in ReadOnlyMemory<T> buffer) + public ForwardOnlyMemoryReader(ReadOnlyMemory<T> buffer) { _segment = buffer; _size = buffer.Length; @@ -54,6 +54,7 @@ namespace VNLib.Utils.Memory /// The remaining data window /// </summary> public readonly ReadOnlyMemory<T> Window => _segment[_position..]; + /// <summary> /// The number of elements remaining in the window /// </summary> diff --git a/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs b/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs index 4f5286d..e0fbe41 100644 --- a/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs +++ b/lib/Utils/src/Memory/ForwardOnlyMemoryWriter.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -29,16 +29,18 @@ namespace VNLib.Utils.Memory /// <summary> /// Provides a mutable sliding buffer writer /// </summary> - public struct ForwardOnlyMemoryWriter<T> + public record struct ForwardOnlyMemoryWriter<T> { /// <summary> /// The buffer for writing output data to /// </summary> public readonly Memory<T> Buffer { get; } + /// <summary> /// The number of characters written to the buffer /// </summary> public int Written { readonly get; set; } + /// <summary> /// The number of characters remaining in the buffer /// </summary> @@ -53,7 +55,7 @@ namespace VNLib.Utils.Memory /// Creates a new <see cref="ForwardOnlyWriter{T}"/> assigning the specified buffer /// </summary> /// <param name="buffer">The buffer to write data to</param> - public ForwardOnlyMemoryWriter(in Memory<T> buffer) + public ForwardOnlyMemoryWriter(Memory<T> buffer) { Buffer = buffer; Written = 0; @@ -83,6 +85,7 @@ namespace VNLib.Utils.Memory //update char position Written += data.Length; } + /// <summary> /// Appends a single item to the buffer /// </summary> diff --git a/lib/Utils/src/Memory/ForwardOnlyReader.cs b/lib/Utils/src/Memory/ForwardOnlyReader.cs index 9829df3..c109e4b 100644 --- a/lib/Utils/src/Memory/ForwardOnlyReader.cs +++ b/lib/Utils/src/Memory/ForwardOnlyReader.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -43,7 +43,7 @@ namespace VNLib.Utils.Memory /// of the specified type using the specified internal buffer /// </summary> /// <param name="buffer">The buffer to read from</param> - public ForwardOnlyReader(in ReadOnlySpan<T> buffer) + public ForwardOnlyReader(ReadOnlySpan<T> buffer) { _segment = buffer; _size = buffer.Length; @@ -57,7 +57,7 @@ namespace VNLib.Utils.Memory /// </summary> /// <param name="buffer">The buffer to read from</param> /// <param name="offset">The offset within the supplied buffer to begin the reader at</param> - public ForwardOnlyReader(in ReadOnlySpan<T> buffer, int offset) + public ForwardOnlyReader(ReadOnlySpan<T> buffer, int offset) { _segment = buffer[offset..]; _size = _segment.Length; diff --git a/lib/Utils/src/Memory/ForwardOnlyWriter.cs b/lib/Utils/src/Memory/ForwardOnlyWriter.cs index efd7f2b..aa14a5f 100644 --- a/lib/Utils/src/Memory/ForwardOnlyWriter.cs +++ b/lib/Utils/src/Memory/ForwardOnlyWriter.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -35,10 +35,12 @@ namespace VNLib.Utils.Memory /// The buffer for writing output data to /// </summary> public readonly Span<T> Buffer { get; } + /// <summary> /// The number of characters written to the buffer /// </summary> public int Written { readonly get; set; } + /// <summary> /// The number of characters remaining in the buffer /// </summary> @@ -53,7 +55,7 @@ namespace VNLib.Utils.Memory /// Creates a new <see cref="ForwardOnlyWriter{T}"/> assigning the specified buffer /// </summary> /// <param name="buffer">The buffer to write data to</param> - public ForwardOnlyWriter(in Span<T> buffer) + public ForwardOnlyWriter(Span<T> buffer) { Buffer = buffer; Written = 0; @@ -65,7 +67,7 @@ namespace VNLib.Utils.Memory /// </summary> /// <param name="buffer">The buffer to write data to</param> /// <param name="offset">The offset to begin the writer at</param> - public ForwardOnlyWriter(in Span<T> buffer, int offset) + public ForwardOnlyWriter(Span<T> buffer, int offset) { Buffer = buffer[offset..]; Written = 0; @@ -95,6 +97,7 @@ namespace VNLib.Utils.Memory //update char position Written += data.Length; } + /// <summary> /// Appends a single item to the buffer /// </summary> diff --git a/lib/Utils/src/Memory/IStringSerializeable.cs b/lib/Utils/src/Memory/IStringSerializeable.cs index 12cfe52..13b0fd6 100644 --- a/lib/Utils/src/Memory/IStringSerializeable.cs +++ b/lib/Utils/src/Memory/IStringSerializeable.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -37,6 +37,7 @@ namespace VNLib.Utils.Memory /// </summary> /// <returns>A string of the desired representation of the current instance</returns> string Compile(); + /// <summary> /// Compiles the current instance into its safe string representation, and writes its /// contents to the specified buffer writer @@ -44,12 +45,13 @@ namespace VNLib.Utils.Memory /// <param name="writer">The ouput writer to write the serialized representation to</param> /// <exception cref="OutOfMemoryException"></exception> void Compile(ref ForwardOnlyWriter<char> writer); + /// <summary> /// Compiles the current instance into its safe string representation, and writes its /// contents to the specified buffer writer /// </summary> /// <param name="buffer">The buffer to write the serialized representation to</param> /// <returns>The number of characters written to the buffer</returns> - ERRNO Compile(in Span<char> buffer); + ERRNO Compile(Span<char> buffer); } } diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index 26adf85..7369fc0 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -506,34 +506,37 @@ namespace VNLib.Utils.Memory //Check dest bounts CheckBounds(dest, destOffset, count); + //Check if 64bit + if(sizeof(nuint) == 8) + { + //Get the number of bytes to copy + nuint byteCount = ByteCount<T>(count); -#if TARGET_64BIT - //Get the number of bytes to copy - nuint byteCount = ByteCount<T>(count); + //Get memory handle from source + using MemoryHandle srcHandle = source.Pin(0); - //Get memory handle from source - using MemoryHandle srcHandle = source.Pin(0); + //get source offset + T* src = (T*)srcHandle.Pointer + offset; - //get source offset - T* src = (T*)srcHandle.Pointer + offset; + //pin array + fixed (T* dst = dest) + { + //Offset dest ptr + T* dstOffset = dst + destOffset; - //pin array - fixed (T* dst = dest) + //Copy src to set + Buffer.MemoryCopy(src, dstOffset, byteCount, byteCount); + } + } + else { - //Offset dest ptr - T* dstOffset = dst + destOffset; + //If 32bit its safe to use spans - //Copy src to set - Buffer.MemoryCopy(src, dstOffset, byteCount, byteCount); + Span<T> src = source.AsSpan((int)offset, (int)count); + Span<T> dst = dest.AsSpan((int)destOffset, (int)count); + //Copy + src.CopyTo(dst); } -#else - //If 32bit its safe to use spans - - Span<T> src = source.Span.Slice((int)offset, (int)count); - Span<T> dst = dest.AsSpan((int)destOffset, (int)count); - //Copy - src.CopyTo(dst); -#endif } #endregion @@ -645,7 +648,7 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CheckBounds<T>(T[] block, nuint offset, nuint count) { - if (((nuint)block.LongLength - offset) <= count) + if (offset + count > (ulong)block.LongLength) { throw new ArgumentOutOfRangeException("The offset or count is outside of the range of the block of memory"); } |