Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Sources/NIOCore/ByteBuffer-aux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,22 @@ extension ByteBuffer {
return written
}

#if compiler(>=6.2)
/// Write `bytes` into this `ByteBuffer`. Moves the writer index forward by the number of bytes written.
///
/// - Parameters:
/// - bytes: A `RawSpan`
/// - Returns: The number of bytes written or `bytes.byteCount`.
@discardableResult
@inlinable
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
public mutating func writeBytes(_ bytes: RawSpan) -> Int {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we mark this as consuming?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need: we don't consume it. We copy the bytes out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and also Span is copyable, so consuming doesn't have much of a practical effect either. It can, in the best case, elide a copy of the Span object, but that's just two words so it's not really worth the hassle.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah Span is Copyable so this might give the compiler a nudge to optimize it a bit better. At least that's what I was thinking.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If anything, it should be borrowing, right? We're happy to just borrow this -- now as per @Lukasa's point: not much to win here of course.

let written = self.setBytes(bytes, at: self.writerIndex)
self._moveWriterIndex(forwardBy: written)
return written
}
#endif

/// Writes `byte` `count` times. Moves the writer index forward by the number of bytes written.
///
/// - Parameters:
Expand Down
38 changes: 38 additions & 0 deletions Sources/NIOCore/ByteBuffer-core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,34 @@ public struct ByteBuffer {
targetPtr.copyMemory(from: bytes)
}

#if compiler(>=6.2)
@inlinable
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
mutating func _setBytes(_ bytes: RawSpan, at index: _Index) -> _Capacity {
let bytesCount = bytes.byteCount
let newEndIndex: _Index = index + _toIndex(bytesCount)
if !isKnownUniquelyReferenced(&self._storage) {
let extraCapacity = newEndIndex > self._slice.upperBound ? newEndIndex - self._slice.upperBound : 0
self._copyStorageAndRebase(extraCapacity: extraCapacity)
}
self._ensureAvailableCapacity(_Capacity(bytesCount), at: index)
self._setBytesAssumingUniqueBufferAccess(bytes, at: index)
return _toCapacity(bytesCount)
}

@inlinable
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
mutating func _setBytesAssumingUniqueBufferAccess(_ bytes: RawSpan, at index: _Index) {
let targetPtr = UnsafeMutableRawBufferPointer(
rebasing: self._slicedStorageBuffer.dropFirst(Int(index))
)

bytes.withUnsafeBytes {
targetPtr.copyMemory(from: $0)
}
}
#endif

@inline(never)
@inlinable
@_specialize(where Bytes == CircularBuffer<UInt8>)
Expand Down Expand Up @@ -1035,6 +1063,16 @@ extension ByteBuffer {
Int(self._setBytes(bytes, at: _toIndex(index)))
}

#if compiler(>=6.2)
/// Copy `bytes` from a `RawSpan` into the `ByteBuffer` at `index`. Does not move the writer index.
@discardableResult
@inlinable
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
public mutating func setBytes(_ bytes: RawSpan, at index: Int) -> Int {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here?

Int(self._setBytes(bytes, at: _toIndex(index)))
}
#endif

/// Move the reader index forward by `offset` bytes.
///
/// - warning: By contract the bytes between (including) `readerIndex` and (excluding) `writerIndex` must be
Expand Down
43 changes: 43 additions & 0 deletions Tests/NIOCoreTests/ByteBufferTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4420,3 +4420,46 @@ extension ByteBufferTest {
XCTAssertNil(result, "peekUUIDBytes() should return nil when fewer than 16 bytes are readable.")
}
}

#if compiler(>=6.2)
extension ByteBufferTest {
@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
func testWriteBytesRawSpan() {
// Write 16 bytes into buffer using a RawSpan
let byteArray: [UInt8] = Array(0..<16)
let rawSpan = byteArray.span.bytes
let writeLength = self.buf.writeBytes(rawSpan)
XCTAssertEqual(writeLength, rawSpan.byteCount)

let read = self.buf.readBytes(length: 4)
XCTAssertEqual([0, 1, 2, 3], read)
XCTAssertEqual(buf.readerIndex, 4)

let peek = self.buf.peekBytes(length: 4)
XCTAssertEqual([4, 5, 6, 7], peek)
XCTAssertEqual(buf.readerIndex, 4)

let rest = self.buf.readBytes(length: 12)
XCTAssertEqual([4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], rest)
XCTAssertEqual(buf.readerIndex, 16)
}

@available(macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26, *)
func testSetBytesRawSpan() {
// Write 4 bytes using setBytes
let byteArray: [UInt8] = Array(0..<4)
let rawSpan = byteArray.span.bytes
let writeLength = self.buf.setBytes(rawSpan, at: 0)
XCTAssertEqual(writeLength, rawSpan.byteCount)

// Should not be readable as writer index is not moved by setBytes
let shouldBeNil = self.buf.readBytes(length: 4)
XCTAssertNil(shouldBeNil)

// Move writer index
self.buf.moveWriterIndex(to: 4)
let result = self.buf.readBytes(length: 4)
XCTAssertEqual(Array(0..<4), result!)
}
}
#endif