From b313f4766054017a6894a947d92352869cc2b873 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Fri, 10 Nov 2023 20:09:29 +0000 Subject: [PATCH] Implement Stream.Read(Span) overloads. The default Stream implementation of this method has to rent an array so it can call the overload that accepts an array, and then copy the output over. This is because the array overload is required and the span overload was only added more recently. We can avoid the overhead of this by implementing the span overload and working with the destination span directly. Do so for all classes we have that derive from Stream, and redirect their array overload to the span overload for code reuse. --- OpenRA.Game/Primitives/MergedStream.cs | 20 ++++++++++++---- .../Primitives/ReadOnlyAdapterStream.cs | 24 ++++++++++++------- OpenRA.Game/Primitives/SegmentStream.cs | 16 ++++++++++--- OpenRA.Mods.Cnc/AudioLoaders/VocLoader.cs | 20 +++++++++------- OpenRA.Mods.Common/AudioLoaders/OggLoader.cs | 12 +++++++--- 5 files changed, 64 insertions(+), 28 deletions(-) diff --git a/OpenRA.Game/Primitives/MergedStream.cs b/OpenRA.Game/Primitives/MergedStream.cs index dfa5266eb7..84f1844ec7 100644 --- a/OpenRA.Game/Primitives/MergedStream.cs +++ b/OpenRA.Game/Primitives/MergedStream.cs @@ -80,18 +80,23 @@ namespace OpenRA.Primitives } public override int Read(byte[] buffer, int offset, int count) + { + return Read(buffer.AsSpan(offset, count)); + } + + public override int Read(Span buffer) { int bytesRead; if (position >= Stream1.Length) - bytesRead = Stream2.Read(buffer, offset, count); - else if (count > Stream1.Length) + bytesRead = Stream2.Read(buffer); + else if (buffer.Length > Stream1.Length) { - bytesRead = Stream1.Read(buffer, offset, (int)Stream1.Length); - bytesRead += Stream2.Read(buffer, (int)Stream1.Length, count - (int)Stream1.Length); + bytesRead = Stream1.Read(buffer[..(int)Stream1.Length]); + bytesRead += Stream2.Read(buffer[(int)Stream1.Length..]); } else - bytesRead = Stream1.Read(buffer, offset, count); + bytesRead = Stream1.Read(buffer); position += bytesRead; @@ -108,6 +113,11 @@ namespace OpenRA.Primitives throw new NotSupportedException(); } + public override void Write(ReadOnlySpan buffer) + { + throw new NotSupportedException(); + } + public override bool CanRead => Stream1.CanRead && Stream2.CanRead; public override bool CanSeek => Stream1.CanSeek && Stream2.CanSeek; diff --git a/OpenRA.Game/Primitives/ReadOnlyAdapterStream.cs b/OpenRA.Game/Primitives/ReadOnlyAdapterStream.cs index c8f2f39817..9931df2217 100644 --- a/OpenRA.Game/Primitives/ReadOnlyAdapterStream.cs +++ b/OpenRA.Game/Primitives/ReadOnlyAdapterStream.cs @@ -51,6 +51,7 @@ namespace OpenRA.Primitives public sealed override void SetLength(long value) { throw new NotSupportedException(); } public sealed override void WriteByte(byte value) { throw new NotSupportedException(); } public sealed override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } + public override void Write(ReadOnlySpan buffer) { throw new NotSupportedException(); } public sealed override void Flush() { throw new NotSupportedException(); } public sealed override int ReadByte() @@ -70,31 +71,36 @@ namespace OpenRA.Primitives public sealed override int Read(byte[] buffer, int offset, int count) { - var copied = 0; - ConsumeData(buffer, offset, count, ref copied); + return Read(buffer.AsSpan(offset, count)); + } - while (copied < count && !baseStreamEmpty) + public override int Read(Span buffer) + { + var copied = 0; + ConsumeData(buffer, ref copied); + + while (copied < buffer.Length && !baseStreamEmpty) { baseStreamEmpty = BufferData(baseStream, data); - ConsumeData(buffer, offset, count, ref copied); + ConsumeData(buffer, ref copied); } return copied; } /// - /// Reads data into a buffer, which will be used to satisfy and - /// calls. + /// Reads data into a buffer, which will be used to satisfy , + /// and calls. /// /// The source stream from which bytes should be read. /// The queue where bytes should be enqueued. Do not dequeue from this buffer. /// Return true if all data has been read; otherwise, false. protected abstract bool BufferData(Stream baseStream, Queue data); - void ConsumeData(byte[] buffer, int offset, int count, ref int copied) + void ConsumeData(Span buffer, ref int copied) { - while (copied < count && data.Count > 0) - buffer[offset + copied++] = data.Dequeue(); + while (copied < buffer.Length && data.Count > 0) + buffer[copied++] = data.Dequeue(); } protected override void Dispose(bool disposing) diff --git a/OpenRA.Game/Primitives/SegmentStream.cs b/OpenRA.Game/Primitives/SegmentStream.cs index d71af205ec..415ba70631 100644 --- a/OpenRA.Game/Primitives/SegmentStream.cs +++ b/OpenRA.Game/Primitives/SegmentStream.cs @@ -68,11 +68,16 @@ namespace OpenRA.Primitives } public override int Read(byte[] buffer, int offset, int count) + { + return Read(buffer.AsSpan(offset, count)); + } + + public override int Read(Span buffer) { var remaining = Length - Position; if (remaining <= 0) return 0; - return BaseStream.Read(buffer, offset, (int)Math.Min(remaining, count)); + return BaseStream.Read(buffer[..(int)Math.Min(remaining, buffer.Length)]); } public override void WriteByte(byte value) @@ -84,9 +89,14 @@ namespace OpenRA.Primitives public override void Write(byte[] buffer, int offset, int count) { - if (Position + count >= Length) + Write(buffer.AsSpan(offset, count)); + } + + public override void Write(ReadOnlySpan buffer) + { + if (Position + buffer.Length >= Length) throw new IOException("Attempted to write past the end of the stream."); - BaseStream.Write(buffer, offset, count); + BaseStream.Write(buffer); } public override void Flush() { BaseStream.Flush(); } diff --git a/OpenRA.Mods.Cnc/AudioLoaders/VocLoader.cs b/OpenRA.Mods.Cnc/AudioLoaders/VocLoader.cs index ec725f12e4..bb684d4810 100644 --- a/OpenRA.Mods.Cnc/AudioLoaders/VocLoader.cs +++ b/OpenRA.Mods.Cnc/AudioLoaders/VocLoader.cs @@ -332,18 +332,16 @@ namespace OpenRA.Mods.Cnc.AudioLoaders } } - int Read(byte[] buffer, int offset, int count) + int Read(Span buffer) { var bytesWritten = 0; - var samplesLeft = Math.Min(count, buffer.Length - offset); - while (samplesLeft > 0) + while (buffer.Length > 0) { - var len = FillBuffer(samplesLeft); + var len = FillBuffer(buffer.Length); if (len == 0) break; - Buffer.BlockCopy(this.buffer, 0, buffer, offset, len); - samplesLeft -= len; - offset += len; + this.buffer.AsSpan(..len).CopyTo(buffer); + buffer = buffer[len..]; bytesWritten += len; } @@ -372,13 +370,19 @@ namespace OpenRA.Mods.Cnc.AudioLoaders public override int Read(byte[] buffer, int offset, int count) { - return format.Read(buffer, offset, count); + return Read(buffer.AsSpan(offset, count)); + } + + public override int Read(Span buffer) + { + return format.Read(buffer); } public override void Flush() { throw new NotImplementedException(); } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } + public override void Write(ReadOnlySpan buffer) { throw new NotImplementedException(); } protected override void Dispose(bool disposing) { diff --git a/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs b/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs index 2ad8e492f4..c8bd3044f6 100644 --- a/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs +++ b/OpenRA.Mods.Common/AudioLoaders/OggLoader.cs @@ -96,9 +96,14 @@ namespace OpenRA.Mods.Common.AudioLoaders } public override int Read(byte[] buffer, int offset, int count) + { + return Read(buffer.AsSpan(offset, count)); + } + + public override int Read(Span buffer) { // Adjust count so it is in 16-bit samples instead of bytes. - count /= 2; + var count = buffer.Length / 2; // Make sure we don't have an odd count. count -= count % format.reader.Channels; @@ -112,8 +117,8 @@ namespace OpenRA.Mods.Common.AudioLoaders for (var i = 0; i < samples; i++) { var conversion = (short)(floatBuffer[i] * 32767); - buffer[offset++] = (byte)(conversion & 255); - buffer[offset++] = (byte)(conversion >> 8); + buffer[i * 2 + 0] = (byte)(conversion & 255); + buffer[i * 2 + 1] = (byte)(conversion >> 8); } // Adjust count back to bytes. @@ -124,6 +129,7 @@ namespace OpenRA.Mods.Common.AudioLoaders public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } + public override void Write(ReadOnlySpan buffer) { throw new NotImplementedException(); } protected override void Dispose(bool disposing) {