Add OutputStream.getWritableBytes(n)
This commit is contained in:
parent
94fa3fab8c
commit
5a7d1d196f
3 changed files with 70 additions and 6 deletions
|
|
@ -473,6 +473,10 @@ bytes before continuing. The rationale here is that introducing an interruption
|
||||||
point at every `write` produces less optimal code, but if this is desired you
|
point at every `write` produces less optimal code, but if this is desired you
|
||||||
can use the `stream.writeAndWait` API.
|
can use the `stream.writeAndWait` API.
|
||||||
|
|
||||||
|
If you have existing algorithms that output data to an `openArray`, you can use
|
||||||
|
the `stream.getWritableBytes` API to continue using them without introducing any
|
||||||
|
intermediate buffers.
|
||||||
|
|
||||||
#### Delayed Writes
|
#### Delayed Writes
|
||||||
|
|
||||||
Many protocols and formats employ fixed-size and variable-size length prefixes
|
Many protocols and formats employ fixed-size and variable-size length prefixes
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ template flushImpl(s: OutputStream, awaiter, writeOp, flushOp: untyped) =
|
||||||
trackWrittenTo(s.buffers, s.span.startAddr)
|
trackWrittenTo(s.buffers, s.span.startAddr)
|
||||||
awaiter s.vtable.writeOp(s, nil, 0)
|
awaiter s.vtable.writeOp(s, nil, 0)
|
||||||
s.span = getWritableSpan(s.buffers)
|
s.span = getWritableSpan(s.buffers)
|
||||||
s.spanEndPos += s.span.len - runway
|
s.spanEndPos = s.spanEndPos - runway + s.span.len
|
||||||
|
|
||||||
if s.vtable.flushOp != nil:
|
if s.vtable.flushOp != nil:
|
||||||
awaiter s.vtable.flushOp(s)
|
awaiter s.vtable.flushOp(s)
|
||||||
|
|
@ -223,7 +223,7 @@ proc ensureRunway*(s: OutputStream, neededRunway: Natural) =
|
||||||
# feeding to it.
|
# feeding to it.
|
||||||
fsAssert s.buffers != nil, "Unsafe memory output of insufficient size"
|
fsAssert s.buffers != nil, "Unsafe memory output of insufficient size"
|
||||||
s.buffers.ensureRunway(s.span, neededRunway)
|
s.buffers.ensureRunway(s.span, neededRunway)
|
||||||
s.spanEndPos += (s.span.len - runway)
|
s.spanEndPos = s.spanEndPos - runway + s.span.len
|
||||||
|
|
||||||
when fsAsyncSupport:
|
when fsAsyncSupport:
|
||||||
template ensureRunway*(s: AsyncOutputStream, neededRunway: Natural) =
|
template ensureRunway*(s: AsyncOutputStream, neededRunway: Natural) =
|
||||||
|
|
@ -472,7 +472,55 @@ proc delayVarSizeWrite*(s: OutputStream, maxSize: Natural): VarSizeWriteCursor =
|
||||||
|
|
||||||
s.span = PageSpan(startAddr: cursorEndAddr,
|
s.span = PageSpan(startAddr: cursorEndAddr,
|
||||||
endAddr: nextPageSpan.endAddr)
|
endAddr: nextPageSpan.endAddr)
|
||||||
s.spanEndPos += nextPageSize - runway
|
s.spanEndPos = s.spanEndPos - runway + nextPageSize
|
||||||
|
|
||||||
|
proc getWritableBytesOnANewPage(s: OutputStream, spanSize: Natural): ptr byte =
|
||||||
|
fsAssert s.buffers != nil
|
||||||
|
|
||||||
|
trackWrittenTo(s.buffers, s.span.startAddr)
|
||||||
|
|
||||||
|
let
|
||||||
|
nextPageSize = nextAlignedSize(spanSize, s.buffers.pageSize)
|
||||||
|
nextPage = allocWritablePage(nextPageSize, spanSize)
|
||||||
|
|
||||||
|
s.buffers.queue.addLast nextPage
|
||||||
|
|
||||||
|
let retiredSpanRunway = s.span.len
|
||||||
|
s.span = nextPage.fullSpan
|
||||||
|
s.spanEndPos = s.spanEndPos - retiredSpanRunway + spanSize
|
||||||
|
s.span.startAddr
|
||||||
|
|
||||||
|
template getWritableBytes*(sp: OutputStream, spanSizeParam: Natural): openArray[byte] =
|
||||||
|
## Returns a contiguous range of memory that the caller is free to populate fully
|
||||||
|
## or partially. The caller indicates how many bytes were written to the span by
|
||||||
|
## calling `advance(numberOfBytes)` once or multiple times. Advancing the stream
|
||||||
|
## past the allocated span size is considered a defect. The typical usage pattern
|
||||||
|
## of this API looks as follows:
|
||||||
|
##
|
||||||
|
## stream.advance(myComponent.writeBlock(stream.getWritableBytes(maxBlockSize)))
|
||||||
|
##
|
||||||
|
## In the example, `writeBlock` would be a function returning the number of bytes
|
||||||
|
## written to the span.
|
||||||
|
##
|
||||||
|
## While it's not illegal to issue other writing operations to the stream during
|
||||||
|
## the `getWritetableSpan` -> `advance` sequence, doing this is not recommended
|
||||||
|
## because it will result in overwriting the same range of bytes.
|
||||||
|
let
|
||||||
|
s = sp
|
||||||
|
spanSize = spanSizeParam
|
||||||
|
runway = s.span.len
|
||||||
|
startAddr = if spanSize <= runway:
|
||||||
|
s.span.startAddr
|
||||||
|
else:
|
||||||
|
getWritableBytesOnANewPage(s, spanSize)
|
||||||
|
|
||||||
|
makeOpenArray(startAddr, spanSize)
|
||||||
|
|
||||||
|
proc advance*(s: OutputStream, bytesWrittenToWritableSpan: Natural) =
|
||||||
|
## Advance the stream write cursor.
|
||||||
|
## Typically used after a previous call to `getWritableBytes`.
|
||||||
|
fsAssert bytesWrittenToWritableSpan <= s.span.len
|
||||||
|
s.span.startAddr = offset(s.span.startAddr, bytesWrittenToWritableSpan)
|
||||||
|
|
||||||
proc finalize*(cursor: var WriteCursor) =
|
proc finalize*(cursor: var WriteCursor) =
|
||||||
fsAssert cursor.stream.extCursorsCount > 0
|
fsAssert cursor.stream.extCursorsCount > 0
|
||||||
|
|
@ -776,7 +824,7 @@ proc consumeOutputsImpl(s: OutputStream, consumer: OutputConsumingProc) =
|
||||||
consumer(makeOpenArray(pageReadableStart, pageLen))
|
consumer(makeOpenArray(pageReadableStart, pageLen))
|
||||||
|
|
||||||
s.span = getWritableSpan(s.buffers)
|
s.span = getWritableSpan(s.buffers)
|
||||||
s.spanEndPos += s.span.len - runway
|
s.spanEndPos = s.spanEndPos - runway + s.span.len
|
||||||
|
|
||||||
template consumeOutputs*(s: OutputStream, bytesVar, body: untyped) =
|
template consumeOutputs*(s: OutputStream, bytesVar, body: untyped) =
|
||||||
## Please note that calling `consumeOutputs` on an unbuffered stream
|
## Please note that calling `consumeOutputs` on an unbuffered stream
|
||||||
|
|
@ -821,7 +869,7 @@ proc consumeContiguousOutputImpl(s: OutputStream, consumer: OutputConsumingProc)
|
||||||
consumer(makeOpenArray(bytesPtr, bytesLen))
|
consumer(makeOpenArray(bytesPtr, bytesLen))
|
||||||
|
|
||||||
s.span = s.buffers.getWritableSpan()
|
s.span = s.buffers.getWritableSpan()
|
||||||
s.spanEndPos += s.span.len - runway
|
s.spanEndPos = s.spanEndPos - runway + s.span.len
|
||||||
|
|
||||||
template consumeContiguousOutput*(s: OutputStream, bytesVar, body: untyped) =
|
template consumeContiguousOutput*(s: OutputStream, bytesVar, body: untyped) =
|
||||||
## Please note that calling `consumeContiguousOutput` on an unbuffered stream
|
## Please note that calling `consumeContiguousOutput` on an unbuffered stream
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,11 @@ suite "output stream":
|
||||||
|
|
||||||
checkOutputsMatch()
|
checkOutputsMatch()
|
||||||
|
|
||||||
|
proc writeBlock(data: openArray[byte], output: openArray[byte]): int =
|
||||||
|
doAssert data.len <= output.len
|
||||||
|
copyMem(unsafeAddr output[0], unsafeAddr data[0], data.len)
|
||||||
|
data.len
|
||||||
|
|
||||||
suite "randomized tests":
|
suite "randomized tests":
|
||||||
type
|
type
|
||||||
WriteTypes = enum
|
WriteTypes = enum
|
||||||
|
|
@ -357,6 +362,13 @@ suite "randomized tests":
|
||||||
for i in 0 .. 3000:
|
for i in 0 .. 3000:
|
||||||
let bytes = randomBytes(rand(9999) + 1)
|
let bytes = randomBytes(rand(9999) + 1)
|
||||||
referenceBytes.add bytes
|
referenceBytes.add bytes
|
||||||
|
|
||||||
|
var openArraySize = rand(12000)
|
||||||
|
if openArraySize >= bytes.len:
|
||||||
|
# Make sure that sometimes `writeBlock` populates the entire span
|
||||||
|
if i < 100: openArraySize = bytes.len
|
||||||
|
output.advance writeBlock(bytes, output.getWritableBytes(openArraySize))
|
||||||
|
else:
|
||||||
output.write bytes
|
output.write bytes
|
||||||
|
|
||||||
close output
|
close output
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue