Skip to content
Open
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
70 changes: 70 additions & 0 deletions src/internal/io_buffer/README.mbt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# internal/io_buffer

Sliding-window buffer used by the async I/O stack.

## Overview

`@internal/io_buffer` provides a small, low-level `Buffer` type used by
`@io.ReaderBuffer` and other internal readers. The buffer tracks three pieces of
state:

- `buf`: the underlying `FixedArray[Byte]`
- `start`: offset of the first valid byte
- `len`: number of valid bytes

This package is intentionally minimal and does not enforce invariants beyond
these fields. It is meant for internal use.

## API

- `Buffer::new(size)` creates an empty buffer with the given capacity.
- `Buffer::clear` drops data and releases storage.
- `Buffer::enlarge_to(n)` ensures there is space for `n` bytes starting from
`start`, compacting or reallocating as needed.
- `Buffer::drop(n)` consumes `n` bytes from the front.

## Examples

```mbt check
///|
test "basic window management" {
let buf = @io_buffer.Buffer::new(6)
buf.buf[0] = b'a'
buf.buf[1] = b'b'
buf.buf[2] = b'c'
buf.len = 3

// Consume two bytes.
@io_buffer.Buffer::drop(buf, 2)
inspect(buf.start, content="2")
inspect(buf.len, content="1")
}
```

```mbt check
///|
test "compaction preserves data" {
let buf = @io_buffer.Buffer::new(5)
buf.buf[3] = b'x'
buf.buf[4] = b'y'
buf.start = 3
buf.len = 2
@io_buffer.Buffer::enlarge_to(buf, 4)
let view = buf.buf.unsafe_reinterpret_as_bytes()[:buf.len]
inspect(view, content="b\"xy\"")
}
```

```mbt check
///|
test "resize rounds up to 1024 bytes" {
let buf = @io_buffer.Buffer::new(4)
@io_buffer.Buffer::enlarge_to(buf, 7)
inspect(buf.buf.length(), content="1024")
}
```

## Notes

- The reallocation size is rounded up to 1024-byte segments.
- `clear` releases storage by replacing the internal buffer with an empty array.
100 changes: 100 additions & 0 deletions src/internal/io_buffer/read_buffer.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,54 @@
// limitations under the License.

///|
/// Internal byte buffer with a sliding window.
///
/// `buf` stores the underlying bytes, `start` is the offset of the first valid
/// byte, and `len` is the number of valid bytes available. The buffer does not
/// enforce invariants beyond these fields; callers are responsible for keeping
/// them consistent.
pub(all) struct Buffer {
mut buf : FixedArray[Byte]
mut start : Int
mut len : Int
}

///|
/// Create a new buffer with the given capacity.
///
/// The buffer starts empty (`start = 0`, `len = 0`).
///
/// # Example
/// ```mbt check
/// test "new buffer is empty" {
/// let buf = Buffer::new(8)
/// inspect(buf.buf.length(), content="8")
/// inspect(buf.start, content="0")
/// inspect(buf.len, content="0")
/// }
/// ```
#as_free_fn
pub fn Buffer::new(size : Int) -> Buffer {
{ buf: FixedArray::make(size, 0), start: 0, len: 0 }
}

///|
/// Clear the buffer and release its storage.
///
/// After `clear`, the buffer has zero capacity and no valid bytes.
///
/// # Example
/// ```mbt check
/// test "clear resets state" {
/// let buf = Buffer::new(4)
/// buf.start = 1
/// buf.len = 2
/// Buffer::clear(buf)
/// inspect(buf.buf.length(), content="0")
/// inspect(buf.start, content="0")
/// inspect(buf.len, content="0")
/// }
/// ```
pub fn Buffer::clear(buf : Buffer) -> Unit {
buf.buf = []
buf.start = 0
Expand All @@ -36,6 +71,45 @@ pub fn Buffer::clear(buf : Buffer) -> Unit {
const SEGMENT_SIZE = 1024

///|
/// Ensure the buffer can hold at least `n` bytes starting from `start`.
///
/// - If there is enough space after `start`, nothing changes.
/// - If the capacity is enough but space after `start` is not, the buffer is
/// compacted so valid bytes start at offset 0.
/// - If the capacity is insufficient, a new buffer is allocated with size
/// rounded up to a 1024-byte segment.
///
/// # Example
/// ```mbt check
/// test "enlarge compacts in-place" {
/// let buf = Buffer::new(5)
/// buf.buf[3] = b'x'
/// buf.buf[4] = b'y'
/// buf.start = 3
/// buf.len = 2
/// Buffer::enlarge_to(buf, 4)
/// inspect(buf.start, content="0")
/// inspect(buf.buf.length(), content="5")
/// inspect(buf.len, content="2")
/// let view = buf.buf.unsafe_reinterpret_as_bytes()[:buf.len]
/// inspect(view, content="b\"xy\"")
/// }
/// ```
///
/// ```mbt check
/// test "enlarge grows to segment size" {
/// let buf = Buffer::new(4)
/// buf.buf[1] = b'a'
/// buf.buf[2] = b'b'
/// buf.start = 1
/// buf.len = 2
/// Buffer::enlarge_to(buf, 7)
/// inspect(buf.start, content="0")
/// inspect(buf.buf.length(), content="1024")
/// let view = buf.buf.unsafe_reinterpret_as_bytes()[:buf.len]
/// inspect(view, content="b\"ab\"")
/// }
/// ```
pub fn Buffer::enlarge_to(self : Buffer, n : Int) -> Unit {
let capacity = self.buf.length()
if self.start + n <= capacity {
Expand All @@ -55,6 +129,32 @@ pub fn Buffer::enlarge_to(self : Buffer, n : Int) -> Unit {
}

///|
/// Drop `n` bytes from the front of the buffer.
///
/// If all bytes are dropped, `start` is reset to 0.
///
/// # Example
/// ```mbt check
/// test "drop advances window" {
/// let buf = Buffer::new(6)
/// buf.start = 1
/// buf.len = 4
/// Buffer::drop(buf, 2)
/// inspect(buf.start, content="3")
/// inspect(buf.len, content="2")
/// }
/// ```
///
/// ```mbt check
/// test "drop to empty resets start" {
/// let buf = Buffer::new(3)
/// buf.start = 2
/// buf.len = 1
/// Buffer::drop(buf, 1)
/// inspect(buf.start, content="0")
/// inspect(buf.len, content="0")
/// }
/// ```
pub fn Buffer::drop(self : Buffer, n : Int) -> Unit {
guard n <= self.len
self.len -= n
Expand Down