diff --git a/CHANGELOG.md b/CHANGELOG.md index 03ce9d6..608d9b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] - yyyy-mm-dd ### Added +- Added RcSpans UnitTest + ### Fixed ### Changed ### Removed ### Special Thanks +- [@Doprez](https://github.com/Doprez) ## [2024.2.1] - 2024-05-04 @@ -20,7 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Added struct DtCrowdScopedTimer to avoid allocations in scoped timer calls. [@wrenge](https://github.com/wrenge) - Added struct RcScopedTimer to avoid allocations in RcContext scoped timer [@ikpil](https://github.com/ikpil) - Added RcSpans [@ikpil](https://github.com/ikpil) -- + ### Fixed - SOH issue [#14](https://github.com/ikpil/DotRecast/issues/41) - Optimization: reduce number of allocations on hot path. [@awgil](https://github.com/awgil) diff --git a/src/DotRecast.Core/RcSpans.cs b/src/DotRecast.Core/RcSpans.cs index 474f492..283defc 100644 --- a/src/DotRecast.Core/RcSpans.cs +++ b/src/DotRecast.Core/RcSpans.cs @@ -6,17 +6,25 @@ namespace DotRecast.Core public static class RcSpans { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(Span source, Span destination) + public static void Copy(Span src, Span dst) { - Copy(source, 0, destination, 0, source.Length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(Span source, int sourceIdx, Span destination, int destinationIdx, int length) - { - var src = source.Slice(sourceIdx, length); - var dst = destination.Slice(destinationIdx); src.CopyTo(dst); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy(Span src, int srcIdx, Span dst, int dstIdx, int length) + { + var slicedSrc = src.Slice(srcIdx, length); + var slicedDst = dst.Slice(dstIdx); + slicedSrc.CopyTo(slicedDst); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Move(Span src, int srcIdx, int dstIdx, int length) + { + var slicedSrc = src.Slice(srcIdx, length); + var slicedDst = src.Slice(dstIdx, length); + slicedSrc.CopyTo(slicedDst); + } } } \ No newline at end of file diff --git a/test/DotRecast.Core.Test/RcSpanTest.cs b/test/DotRecast.Core.Test/RcSpanTest.cs new file mode 100644 index 0000000..268db7a --- /dev/null +++ b/test/DotRecast.Core.Test/RcSpanTest.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace DotRecast.Core.Test; + +public class RcSpanTest +{ + [Test] + public void TestCopy() + { + // Test for copying all elements to the destination span. + { + Span src = stackalloc long[] { 1, 2, 3 }; + Span dst = stackalloc long[] { 0, 0, 0 }; + + RcSpans.Copy(src, dst); + Assert.That(src.ToArray(), Is.EqualTo(dst.ToArray())); + } + + // Test for successful copying when the destination Span has a larger size. + { + Span src = stackalloc long[] { 1, 2 }; + Span dst = stackalloc long[] { 0, 0, 0, 0 }; + + RcSpans.Copy(src, dst); + Assert.That(src.ToArray(), Is.EqualTo(dst.Slice(0, src.Length).ToArray())); + } + + // Test for an error when copying to a Span with a smaller size. + { + Assert.Throws(() => + { + Span src = stackalloc long[] { 1, 2, 3, 4, 5 }; + Span dst = stackalloc long[] { 0, 0, 0 }; + RcSpans.Copy(src, dst); + }); + } + + // Test for copying a specific range of elements from the source span to a specific range in the destination span. + { + Span src = stackalloc long[] { 1, 2, 3, 4, 5 }; + Span dst = stackalloc long[] { 0, 0, 0 }; + + RcSpans.Copy(src, 2, dst, 0, 2); + Assert.That(src.Slice(2, 2).ToArray(), Is.EqualTo(dst.Slice(0, 2).ToArray())); + } + + // Test for copying a specific range of elements from the source span to a specific range in the destination span. + { + Span src = stackalloc long[] { 5, 4, 3, 2, 1 }; + Span dst = stackalloc long[] { 0, 0, 0 }; + + RcSpans.Copy(src, 2, dst, 0, 3); + Assert.That(src.Slice(2, 3).ToArray(), Is.EqualTo(dst.ToArray())); + } + + // Test for src (index + length) over + Assert.Throws(() => + { + Span src = stackalloc long[] { 5, 4, 3, 2, 1 }; + Span dst = stackalloc long[] { 0, 0, 0 }; + + // + RcSpans.Copy(src, 3, dst, 0, 3); + }); + + // Test for src (index + length) over + Assert.Throws(() => + { + Span src = stackalloc long[] { 5, 4, 3, 2, 1 }; + Span dst = stackalloc long[] { 0, 0, 0 }; + + // + RcSpans.Copy(src, 5, dst, 0, 1); + }); + + + // Test for dst (idx + length) over + Assert.Throws(() => + { + Span src = stackalloc long[] { 5, 4, 3, 2, 1 }; + Span dst = stackalloc long[] { 0, 0, 0 }; + + // + RcSpans.Copy(src, 0, dst, 1, 3); + }); + + // Test for dst (idx + length) over + Assert.Throws(() => + { + Span src = stackalloc long[] { 5, 4, 3, 2, 1 }; + Span dst = stackalloc long[] { 0, 0, 0 }; + + // + RcSpans.Copy(src, 0, dst, 0, 4); + }); + } + + [Test] + public void TestMove() + { + // [3, 2, 1] -> [3, 1, 1] + { + var expected = new List() { 3, 1, 1 }; + Span src = stackalloc long[] { 3, 2, 1 }; + RcSpans.Move(src, 2, 1, 1); + Assert.That(src.ToArray(), Is.EqualTo(expected)); + } + + // [3, 2, 1] -> [2, 1, 1] + { + var expected = new List() { 2, 1, 1 }; + Span src = stackalloc long[] { 3, 2, 1 }; + RcSpans.Move(src, 1, 0, 2); + Assert.That(src.ToArray(), Is.EqualTo(expected)); + } + + // [3, 2, 1] -> [3, 2, 1] + { + var expected = new List() { 3, 2, 1 }; + Span src = stackalloc long[] { 3, 2, 1 }; + RcSpans.Move(src, 0, 0, 3); + Assert.That(src.ToArray(), Is.EqualTo(expected)); + } + + // length over + Assert.Throws(() => + { + Span src = stackalloc long[] { 3, 2, 1 }; + RcSpans.Move(src, 0, 0, 4); + }); + + // source index over + Assert.Throws(() => + { + Span src = stackalloc long[] { 3, 2, 1 }; + RcSpans.Move(src, 3, 0, 1); + }); + + // destination index over + Assert.Throws(() => + { + Span src = stackalloc long[] { 3, 2, 1 }; + RcSpans.Move(src, 0, 3, 1); + }); + } +} \ No newline at end of file