Skip to content

Commit

Permalink
feat: support Uint8ArrayLists in the same way as Uint8Arrays (#30)
Browse files Browse the repository at this point in the history
In order to support no-copy operations, relax the requirement for `Uint8Array`s to just require a `.byteLength` property since that's all we access.

Generics can then be used to ensure the correct value type is used as normal.

Everything still defaults to `Uint8Array`s so this is non-breaking.
  • Loading branch information
achingbrain authored Aug 2, 2022
1 parent 3e3d665 commit 7bae368
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 30 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@
"@types/fast-fifo": "^1.0.0",
"aegir": "^37.0.17",
"it-all": "^1.0.6",
"it-pipe": "^2.0.0"
"it-pipe": "^2.0.0",
"uint8arraylist": "^2.0.0"
},
"repository": {
"type": "git",
Expand Down
35 changes: 10 additions & 25 deletions src/fifo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,41 +57,32 @@ export interface FIFOOptions {
* When the queue reaches this size, it will be split into head/tail parts
*/
splitLimit?: number

/**
* If true, `size` will be the number of items in the queue. If false, all
* values will be interpreted as Uint8Arrays and `size` will be the total
* number of bytes in the queue.
*/
objectMode?: boolean
}

export class FIFO<T> {
public size: number
private readonly hwm: number
private head: FixedFIFO<T>
private tail: FixedFIFO<T>
private readonly objectMode: boolean

constructor (options: FIFOOptions = {}) {
this.hwm = options.splitLimit ?? 16
this.head = new FixedFIFO<T>(this.hwm)
this.tail = this.head
this.size = 0
this.objectMode = Boolean(options.objectMode)
}

calculateSize (obj: any): number {
if (obj?.byteLength != null) {
return obj.byteLength
}

return 1
}

push (val: Next<T>) {
if (val?.value != null) {
if (this.objectMode) {
if (val.value != null) {
this.size++
}
} else if (val.value instanceof Uint8Array) {
this.size += val.value.byteLength
} else {
throw new Error('objectMode was false but tried to push non-Uint8Array value')
}
this.size += this.calculateSize(val.value)
}

if (!this.head.push(val)) {
Expand All @@ -112,13 +103,7 @@ export class FIFO<T> {
}

if (val?.value != null) {
if (this.objectMode) {
this.size--
} else if (val.value instanceof Uint8Array) {
this.size -= val.value.byteLength
} else {
throw new Error('objectMode was false but tried to shift non-Uint8Array value')
}
this.size -= this.calculateSize(val.value)
}

return val
Expand Down
11 changes: 8 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface BytePushableOptions extends Options {
objectMode?: false
}

export function pushable (options?: BytePushableOptions): Pushable<Uint8Array>
export function pushable<T extends { byteLength: number } = Uint8Array> (options?: BytePushableOptions): Pushable<T>
export function pushable<T> (options: ObjectPushableOptions): Pushable<T>
export function pushable<T> (options: Options = {}): Pushable<T> {
const getNext = (buffer: FIFO<T>): NextResult<T> => {
Expand All @@ -57,7 +57,7 @@ export function pushable<T> (options: Options = {}): Pushable<T> {
return _pushable<T, T, Pushable<T>>(getNext, options)
}

export function pushableV (options?: BytePushableOptions): PushableV<Uint8Array>
export function pushableV<T extends { byteLength: number } = Uint8Array> (options?: BytePushableOptions): PushableV<T>
export function pushableV<T> (options: ObjectPushableOptions): PushableV<T>
export function pushableV<T> (options: Options = {}): PushableV<T> {
const getNext = (buffer: FIFO<T>): NextResult<T[]> => {
Expand Down Expand Up @@ -97,7 +97,7 @@ export function pushableV<T> (options: Options = {}): PushableV<T> {
function _pushable<PushType, ValueType, ReturnType> (getNext: getNext<PushType, ValueType>, options?: Options): ReturnType {
options = options ?? {}
let onEnd = options.onEnd
let buffer = new FIFO<PushType>(options)
let buffer = new FIFO<PushType>()
let pushable: any
let onNext: ((next: Next<PushType>) => ReturnType) | null
let ended: boolean
Expand Down Expand Up @@ -152,6 +152,11 @@ function _pushable<PushType, ValueType, ReturnType> (getNext: getNext<PushType,
return pushable
}

// @ts-expect-error `byteLength` is not declared on PushType
if (options?.objectMode !== true && value?.byteLength == null) {
throw new Error('objectMode was not true but tried to push non-Uint8Array value')
}

return bufferNext({ done: false, value })
}
const end = (err?: Error) => {
Expand Down
39 changes: 38 additions & 1 deletion test/test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect } from 'aegir/chai'
import { pipe } from 'it-pipe'
import { pushable, pushableV } from '../src/index.js'
import all from 'it-all'
import { Uint8ArrayList } from 'uint8arraylist'

describe('it-pushable', () => {
it('should push input slowly', async () => {
Expand Down Expand Up @@ -359,7 +360,43 @@ describe('it-pushable', () => {
expect(source).to.have.property('readableLength', 0)
})

it('should throw if passed an object when objectMode is false', async () => {
it('should support readableLength for Uint8ArrayLists', async () => {
const source = pushable<Uint8ArrayList>()

expect(source).to.have.property('readableLength', 0)

await source.push(new Uint8ArrayList(Uint8Array.from([1, 2])))
expect(source).to.have.property('readableLength', 2)

await source.push(new Uint8ArrayList(Uint8Array.from([3, 4, 5])))
expect(source).to.have.property('readableLength', 5)

await source.next()
expect(source).to.have.property('readableLength', 3)

await source.next()
expect(source).to.have.property('readableLength', 0)
})

it('should support readableLength for mixed Uint8ArrayLists and Uint8Arrays', async () => {
const source = pushable<Uint8ArrayList | Uint8Array>()

expect(source).to.have.property('readableLength', 0)

await source.push(new Uint8ArrayList(Uint8Array.from([1, 2])))
expect(source).to.have.property('readableLength', 2)

await source.push(Uint8Array.from([3, 4, 5]))
expect(source).to.have.property('readableLength', 5)

await source.next()
expect(source).to.have.property('readableLength', 3)

await source.next()
expect(source).to.have.property('readableLength', 0)
})

it('should throw if passed an object when objectMode is not true', async () => {
const source = pushable()

// @ts-expect-error incorrect argument type
Expand Down

0 comments on commit 7bae368

Please sign in to comment.