requires: {#ExtensibleArray}. provides: {#RingBuffer. #BufferStream. #ReadBufferStream. #WriteBufferStream}. collections addPrototype: #RingBuffer derivedFrom: {Sequence. ExtensibleCollection}. "A RingBuffer is an extensible Sequence designed to re-use exactly one Array in order to minimize memory-manager load. The implementation takes that of ExtensibleArray and overrides it to provide wrap-around semantics and no implicit copying. NOTE: RingBuffer expects its clients to perform the majority of checks on the space remaining before adding to it." RingBuffer addSlot: #contents valued: {}. "The array used for storage." RingBuffer addSlot: #firstIndex valued: 0. "The lower inclusive bound of the used section of the array." RingBuffer addSlot: #lastIndex valued: 0. "The upper exclusive bound of the used section of the array (the address of the first non-buffer element); if this is less than the firstIndex, then the contents themselves surround a gap." b@(RingBuffer traits) clear [ b firstIndex: 0. b lastIndex: 0. b ]. b@(RingBuffer traits) newSize: n [| newB | newB: b clone. newB contents: (b contents newSize: n). newB clear ]. b@(RingBuffer traits) copy [| newB | newB: b clone. newB contents: b contents clone. newB ]. b@(RingBuffer traits) isFull "The buffer is full if the indices span the whole array or are adjacent." [ b lastIndex + 1 = b firstIndex or: [b firstIndex = 0 and: [b contents size - 1 = b lastIndex]] ]. b@(RingBuffer traits) isEmpty "The buffer is empty if the indices are equal." [ b lastIndex = b firstIndex ]. b@(RingBuffer traits) isSplit "Whether the contents are split into two pieces or contiguous." [ b lastIndex < b firstIndex ]. b@(RingBuffer traits) gapSize "How many elements can be added to fill the buffer array." [| x | x: b firstIndex - b lastIndex. b isSplit ifTrue: [x] ifFalse: [b contents size - x] ]. b@(RingBuffer traits) capacity [ b contents size ]. b@(RingBuffer traits) size [ b isSplit ifTrue: [b contents size - b firstIndex + b lastIndex] ifFalse: [b lastIndex - b firstIndex] ]. b@(RingBuffer traits) growTo: n "This should not be called regularly; in fact, RingBuffer never calls this itself. It will provide a new Array of the appropriate capacity and transfer the contents and adjust the indices to match." [| tmp | n > b gapSize ifTrue: [error: 'The contents will not fit.']. tmp: (b contents newSize: n). b isSplit ifTrue: [| offset | offset: tmp size - b contents size. 0 upTo: b lastIndex do: [| :index | tmp at: index put: (b contents at: index)]. b firstIndex below: b contents size do: [| :index | tmp at: index + offset put: (b contents at: index)]. b firstIndex: b firstIndex + offset] ifFalse: [b firstIndex upTo: b lastIndex do: [| :index | tmp at: index put: (b contents at: index)]]. b ]. b@(RingBuffer traits) at: n "The firstIndex is treated as the 0 address. Wrap-around is handled." [| index | index: b firstIndex + n. index >= b contents size ifTrue: [index: index - b contents size]. b contents at: index ]. b@(RingBuffer traits) at: n put: obj "The firstIndex is treated as the 0 address. Wrap-around is handled." [| index | index: b firstIndex + n. index >= b contents size ifTrue: [index: index - b contents size]. b contents at: index put: obj ]. b@(RingBuffer traits) do: block [ b isSplit ifTrue: [b contents from: firstIndex upTo: b contents size - 1 do: block. b contents from: 0 upTo: b lastIndex do: block] ifFalse: [b contents from: firstIndex upTo: b lastIndex do: block] ]. b@(RingBuffer traits) add: obj [ b addLast: obj ]. b@(RingBuffer traits) addAll: c "This is overridden to provide the sequence behavior, as well as because the default Extensible- method just add:s which avoids the size-checking." [ b addAllLast: c ]. b@(RingBuffer traits) addLast: obj [ b isFull ifTrue: [CollectionFull signal]. b contents at: b lastIndex put: obj. b lastIndex: b lastIndex + 1. b lastIndex = b contents size ifTrue: [b lastIndex: 0]. obj ]. b@(RingBuffer traits) addFirst: obj [ b isFull ifTrue: [CollectionFull signal]. b firstIndex = 0 ifTrue: [b firstIndex: b contents size]. b firstIndex: b firstIndex - 1. b contents at: b firstIndex put: obj. obj ]. b@(RingBuffer traits) addAllLast: seq [| dstIndex | seq size > b gapSize ifTrue: [CollectionNotBigEnough signal]. dstIndex: b lastIndex. b lastIndex + seq size >= b contents size ifTrue: [| split | split: b contents size - b lastIndex. 0 below: split do: [| :index | b contents at: dstIndex put: (seq at: index). dstIndex: dstIndex + 1]. dstIndex: 0. seq size - 1 > split ifTrue: [split below: seq size do: [| :index | b contents at: dstIndex put: (seq at: index). dstIndex: dstIndex + 1]]] ifFalse: [seq do: [| :each | b contents at: dstIndex put: each. dstIndex: dstIndex + 1]]. b lastIndex: dstIndex. seq ]. b@(RingBuffer traits) addAllFirst: seq [| dstIndex | seq size > b gapSize ifTrue: [CollectionNotBigEnough signal]. (dstIndex: b firstIndex - seq size) <= 0 ifTrue: [| split | split: dstIndex negated. dstIndex: 0. 0 below: split do: [| :index | dstIndex: dstIndex + 1. b contents at: dstIndex put: (seq at: index)]. dstIndex: b contents size. split < seq size ifTrue: [split below: seq size do: [| :index | dstIndex: dstIndex - 1. b contents at: dstIndex put: (seq at: index)]]. b firstIndex: dstIndex] ifFalse: [dstIndex: b firstIndex - seq size. seq do: [| :each | dstIndex: dstIndex + 1. b contents at: dstIndex put: each]. b firstIndex: b firstIndex - seq size]. seq ]. b@(RingBuffer traits) removeLast [| oldIndex | b lastIndex: (oldIndex: b lastIndex) - 1. oldIndex = 0 ifTrue: [b lastIndex: b contents size - 1]. b contents at: b lastIndex ]. b@(RingBuffer traits) removeFirst [| oldIndex | b firstIndex: (oldIndex: b firstIndex) + 1. b firstIndex = b contents size ifTrue: [b firstIndex: 0]. b contents at: oldIndex ]. b@(RingBuffer traits) removeLast: n [| removed newLast | removed: (b contents newSize: n). newLast: b lastIndex - n. newLast < 0 ifTrue: [| dstIndex | dstIndex: n. b lastIndex downTo: 0 do: [| :i | removed at: (dstIndex: dstIndex - 1) put: (b contents at: i)]. newLast: newLast + b contents size. dstIndex: 0. newLast below: b contents size do: [| :i | removed at: dstIndex put: (b contents at: i). dstIndex: dstIndex + 1]] ifFalse: [0 below: n do: [| :i | removed at: i put: (b contents at: b lastIndex + i)]]. b lastIndex: newLast. removed ]. b@(RingBuffer traits) removeFirst: n [| removed newFirst | removed: (b contents newSize: n). newFirst: b firstIndex + n. newFirst >= b contents size ifTrue: [| dstIndex | dstIndex: 0. b firstIndex below: b contents size do: [| :i | removed at: dstIndex put: (b contents at: i). dstIndex: dstIndex + 1]. newFirst: newFirst - b contents size. 0 below: newFirst do: [| :i | removed at: dstIndex put: (b contents at: i). dstIndex: dstIndex + 1]] ifFalse: [0 below: n do: [| :i | removed at: i put: (b contents at: b firstIndex + i)]]. b firstIndex: newFirst. removed ]. collections addPrototype: #BufferStream derivedFrom: {Stream}. "A BufferStream is a Stream with a buffer array resizable up to a fixed amount, with wrap-around semantics to reduce heavy i/o interaction and avoid stressing the memory-manager." BufferStream addSlot: #stream valued: Stream clone. "The stream being wrapped." BufferStream addSlot: #buffer valued: RingBuffer newEmpty. "The buffer used." b@(BufferStream traits) on: s [ b stream: s. b buffer: b buffer newSameSize. b ]. collections addPrototype: #WriteBufferStream derivedFrom: {BufferStream. WriteStream}. "A BufferStream with semantics for writing." s@(WriteBufferStream traits) flush "Empty the buffer into the stream all at once." [| b | b: s buffer. b isSplit ifTrue: [s nextPutAll: b from: b firstIndex to: b size - 1. s nextPutAll: b from: 0 to: b lastIndex] ifFalse: [s nextPutAll: b from: b firstIndex to: b lastIndex]. b clear. s stream flush. s ]. s@(WriteBufferStream traits) nextPut: obj [ s buffer isFull ifTrue: [s flush]. s buffer addLast: obj ]. s@(WriteBufferStream traits) nextPutAll: seq [ s buffer gapSize < seq size ifFalse: [s flush]. s buffer isSplit ifTrue: [seq doWithIndex: [| :each :index | s contents at: s buffer firstIndex + index put: each]. s firstIndex: s buffer firstIndex + seq size] ifFalse: [| dstIndex | dstIndex: s lastIndex. 0 below: seq size do: [| :each | dstIndex: (dstIndex = s contents size ifTrue: [0] ifFalse: [dstIndex + 1]). s contents at: dstIndex put: each]. s lastIndex: dstIndex]. seq ]. s@(WriteBufferStream traits) nextPutAll: seq from: start to: end "Overridden to use the optimized BufferStream method nextPutAll:." [ s nextPutAll: (seq sliceFrom: start to: end) ]. collections addPrototype: #ReadBufferStream derivedFrom: {BufferStream. ReadStream}. "A BufferStream designed to be read from." s@(ReadBufferStream traits) flush "Fill the buffer from the stream all at once." [| b | b: s buffer. b isSplit ifTrue: [s stream next: b gapSize putInto: b] ifFalse: [s stream next: b capacity - b lastIndex putInto: b. s stream next: b firstIndex putInto: b]. s ]. s@(ReadBufferStream traits) next [ s buffer isEmpty ifTrue: [s stream next: s contents size putInto: s contents. s firstIndex: 0. s lastIndex: 0]. s buffer removeFirst ]. s@(ReadBufferStream traits) next: n putInto: seq@(Sequence traits) [| b oldSize | b: s buffer. oldSize: b size. n > oldSize ifTrue: [b doWithIndex: [| :each :index | seq at: index put: each]. b clear. s stream next: b capacity putInto: b. oldSize below: seq size do: [| :index | seq at: index put: b removeFirst]] ifFalse: [0 below: n do: [| :index | seq at: index put: b removeFirst]]. seq ]. prototypes addPrototype: #ReadWriteBufferStream derivedFrom: {Cloneable}. "This is a rough concept to implement that requires basically two BufferStreams and coordination between them." "TODO: Implement something workable." "Return new BufferStreams of the appropriate type wrapping their arguments." s@(ReadStream traits) buffered [ReadBufferStream newOn: s]. s@(Stream traits) readBuffered [ReadBufferStream newOn: s]. s@(WriteStream traits) buffered [WriteBufferStream newOn: s]. s@(Stream traits) writeBuffered [WriteBufferStream newOn: s].