From 391fb1d854f222370fd4c47a5d3b724d98cf2328 Mon Sep 17 00:00:00 2001 From: Alexandre Mutel Date: Sun, 18 Sep 2022 17:35:39 +0200 Subject: [PATCH] Add `VirtualArray`/`VirtualBuffer` `Append()` and `AppendRange()` methods --- src/Varena.Tests/TestVirtualArray.cs | 33 ++++++++++++++++++ src/Varena.Tests/TestVirtualBuffer.cs | 27 +++++++++++++++ src/Varena/VirtualArray.cs | 49 +++++++++++++++++++++++++++ src/Varena/VirtualBuffer.cs | 28 +++++++++++++++ 4 files changed, 137 insertions(+) diff --git a/src/Varena.Tests/TestVirtualArray.cs b/src/Varena.Tests/TestVirtualArray.cs index 6f7fd49..431db05 100644 --- a/src/Varena.Tests/TestVirtualArray.cs +++ b/src/Varena.Tests/TestVirtualArray.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using System; namespace Varena.Tests { @@ -81,6 +82,38 @@ public void TestAllocateLoop() Assert.Throws(() => buffer.Allocate()); } + [Test] + public void TestAppend() + { + using var manager = new VirtualArenaManager(); + var array = manager.CreateArray("Bytes", 1 << 20); + for (int i = 0; i < 1024; i++) + { + if ((i & 1) == 0) + { + array.Append((byte)i); + } + else + { + array.AppendByRef((byte)i); + } + } + Assert.AreEqual(1024, array.Count); + for (int i = 0; i < array.Count; i++) + { + Assert.AreEqual((byte)i, array[i]); + } + + array.Reset(VirtualArenaResetKind.KeepAllCommitted); + + array.AppendRange(new byte[] { 255, 254, 253, 252 }); + Assert.AreEqual(4, array.Count); + Assert.AreEqual((byte)255, array[0]); + Assert.AreEqual((byte)254, array[1]); + Assert.AreEqual((byte)253, array[2]); + Assert.AreEqual((byte)252, array[3]); + } + [Test] public void TestInvalidArguments() { diff --git a/src/Varena.Tests/TestVirtualBuffer.cs b/src/Varena.Tests/TestVirtualBuffer.cs index 2db24aa..638c7e1 100644 --- a/src/Varena.Tests/TestVirtualBuffer.cs +++ b/src/Varena.Tests/TestVirtualBuffer.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using System; namespace Varena.Tests { @@ -38,6 +39,32 @@ public void TestAllocate() Assert.AreEqual((nuint)0, buffer.AllocatedBytes); } + [Test] + public void TestAppend() + { + using var manager = new VirtualArenaManager(); + var buffer = manager.CreateBuffer("Bytes", 1 << 20); + + for (int i = 0; i < 1024; i++) + { + buffer.Append((byte)i); + } + Assert.AreEqual((nuint)1024, buffer.AllocatedBytes); + var span = buffer.AsSpan(0, 1024); + for (int i = 0; i < span.Length; i++) + { + Assert.AreEqual((byte)i, span[i], "Invalid data in the buffer"); + } + + buffer.Reset(VirtualArenaResetKind.KeepAllCommitted); + buffer.AppendRange(new byte[] { 255, 254, 253, 252,}); + + Assert.AreEqual((nuint)4, buffer.AllocatedBytes); + Assert.AreEqual((byte)255, buffer[0]); + Assert.AreEqual((byte)254, buffer[1]); + Assert.AreEqual((byte)253, buffer[2]); + Assert.AreEqual((byte)252, buffer[3]); + } [Test] public void TestInvalidArguments() diff --git a/src/Varena/VirtualArray.cs b/src/Varena/VirtualArray.cs index 3c64c5e..62761c7 100644 --- a/src/Varena/VirtualArray.cs +++ b/src/Varena/VirtualArray.cs @@ -106,6 +106,55 @@ public Span AllocateRange(int count, out int firstIndex) return AllocateRange(count); } + /// + /// Appends a single element. + /// + /// The value to append. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(T value) + { + unsafe + { + ref var localRef = ref *(T*)base.UnsafeAllocate((nuint)sizeof(T)); + localRef = value; + _count++; + } + } + + /// + /// Appends a single element by reference (for large structs to avoid a copy on the stack). + /// + /// The value to append. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AppendByRef(in T value) + { + unsafe + { + ref var localRef = ref *(T*)base.UnsafeAllocate((nuint)sizeof(T)); + localRef = value; + _count++; + } + } + + /// + /// Appends a range of elements. + /// + /// The range of elements to append. + /// A span to the range of elements appended. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span AppendRange(ReadOnlySpan range) + { + unsafe + { + var count = range.Length; + if (count == 0) return Span.Empty; + var span = new Span(base.UnsafeAllocate((nuint)count * (uint)sizeof(T)), count); + range.CopyTo(span); + _count += count; + return span; + } + } + /// /// Gets the span over all the elements allocated. /// diff --git a/src/Varena/VirtualBuffer.cs b/src/Varena/VirtualBuffer.cs index 0ce2a1f..1d9d948 100644 --- a/src/Varena/VirtualBuffer.cs +++ b/src/Varena/VirtualBuffer.cs @@ -2,6 +2,8 @@ // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. +using System.Runtime.CompilerServices; + namespace Varena; /// @@ -63,6 +65,32 @@ public Span AllocateRange(int count, out ulong firstIndex) return AllocateRange(count); } + /// + /// Appends the specified value to this buffer. + /// + /// The value to append. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void Append(byte value) + { + *(byte*)UnsafeAllocate(1) = value; + } + + /// + /// Appends the specified range to this buffer. + /// + /// The range of values to append. + /// The span of the appended memory. + public Span AppendRange(ReadOnlySpan range) + { + if (range.Length == 0) return new Span(); + unsafe + { + var toSpan = new Span(UnsafeAllocate((nuint)range.Length), range.Length); + range.CopyTo(toSpan); + return toSpan; + } + } + /// /// Gets the span over all the elements allocated. ///