Skip to main content

Basic Operations

Common patterns for reading, writing, and manipulating buffers.

Writing Primitives

Relative Writes (advance position)

val buffer = PlatformBuffer.allocate(1024)

// Signed types
buffer.writeByte(42.toByte())
buffer.writeShort(1000.toShort())
buffer.writeInt(123456)
buffer.writeLong(9876543210L)
buffer.writeFloat(3.14159f)
buffer.writeDouble(2.71828182845)

// Unsigned types
buffer.writeUByte(255.toUByte())
buffer.writeUShort(65535.toUShort())
buffer.writeUInt(4294967295.toUInt())
buffer.writeULong(18446744073709551615uL)

Absolute Writes (position unchanged)

buffer[0] = 42.toByte()
buffer[4] = 1000.toShort()
buffer[8] = 123456
buffer[16] = 9876543210L

Reading Primitives

Relative Reads (advance position)

buffer.resetForRead()

val byte = buffer.readByte()
val short = buffer.readShort()
val int = buffer.readInt()
val long = buffer.readLong()
val float = buffer.readFloat()
val double = buffer.readDouble()

// Unsigned
val uByte = buffer.readUnsignedByte()
val uShort = buffer.readUnsignedShort()
val uInt = buffer.readUnsignedInt()
val uLong = buffer.readUnsignedLong()

Absolute Reads (position unchanged)

val byte = buffer[0]
val short = buffer.getShort(4)
val int = buffer.getInt(8)
val long = buffer.getLong(16)

String Operations

Writing Strings

// UTF-8 encoded (default)
buffer.writeString("Hello, World!")

// With explicit charset
buffer.writeString("Hello", Charset.UTF8)

Reading Strings

// Read fixed number of bytes as UTF-8
val text = buffer.readString(13) // "Hello, World!"

// Read with explicit charset
val text = buffer.readUtf8(bytes = 5)

Byte Array Operations

Writing Byte Arrays

val data = byteArrayOf(1, 2, 3, 4, 5)

// Write entire array
buffer.writeBytes(data)

// Write partial array
buffer.writeBytes(data, offset = 1, length = 3) // writes [2, 3, 4]

Reading Byte Arrays

// Read into new array
val data = buffer.readByteArray(5)

// Read as buffer slice (zero-copy)
val slice = buffer.readBytes(5)

Buffer-to-Buffer Copy

val source = PlatformBuffer.allocate(100)
source.writeString("Hello")
source.resetForRead()

val dest = PlatformBuffer.allocate(100)

// Copy remaining bytes from source to dest
dest.write(source)

Slicing

Create a view without copying:

val original = PlatformBuffer.allocate(100)
original.writeBytes(byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
original.resetForRead()

// Slice bytes 3-7
original.position(3)
original.setLimit(8)
val slice = original.slice()

// slice now contains [4, 5, 6, 7, 8]
// Changes to slice affect original!

Position and Limit Manipulation

val buffer = PlatformBuffer.allocate(100)

// Write some data
buffer.writeInt(1)
buffer.writeInt(2)
buffer.writeInt(3)

// Get current position
val written = buffer.position() // 12

// Prepare for reading
buffer.resetForRead()
// position=0, limit=12

// Skip first int
buffer.position(4)

// Read remaining
val second = buffer.readInt() // 2
val third = buffer.readInt() // 3

Checking Available Data

// Bytes between position and limit
val available = buffer.remaining()

// Check before reading
if (buffer.remaining() >= 4) {
val value = buffer.readInt()
}

Clearing and Resetting

// Reset for writing (position=0, limit=capacity)
buffer.resetForWrite()

// Reset for reading (position=0, limit=previous position)
buffer.resetForRead()

Wrapping Data

// Wrap existing byte array (no copy)
val data = byteArrayOf(0, 0, 0, 42)
val buffer = PlatformBuffer.wrap(data)

val value = buffer.readInt() // 42 (big-endian)

// Wrap with byte order
val littleEndian = PlatformBuffer.wrap(data, ByteOrder.LITTLE_ENDIAN)

Complete Example

fun serializeMessage(id: Int, payload: String, buffer: WriteBuffer) {
// Write header
buffer.writeInt(id)

// Write payload length, then string directly (no intermediary ByteArray)
val lengthPosition = buffer.position()
buffer.writeInt(0) // Placeholder for length

val payloadStart = buffer.position()
buffer.writeString(payload)
val payloadLength = buffer.position() - payloadStart

// Go back and write actual length
val endPosition = buffer.position()
buffer.position(lengthPosition)
buffer.writeInt(payloadLength)
buffer.position(endPosition)
}

fun deserializeMessage(buffer: ReadBuffer): Pair<Int, String> {
val id = buffer.readInt()
val length = buffer.readInt()
val payload = buffer.readString(length)
return id to payload
}

// Usage with buffer pool
pool.withBuffer(1024) { buffer ->
serializeMessage(42, "Hello, World!", buffer)
buffer.resetForRead()
val (id, message) = deserializeMessage(buffer)
}
Avoid Intermediary ByteArrays

Creating intermediate ByteArray copies is a code smell that hurts performance:

// ❌ Anti-pattern: creates temporary ByteArray
val payloadBytes = payload.encodeToByteArray() // Allocation!
buffer.writeBytes(payloadBytes)

// ✅ Better: write string directly
buffer.writeString(payload) // No intermediate allocation

The same applies when reading:

// ❌ Anti-pattern: copies to ByteArray first
val bytes = buffer.readByteArray(length)
processBytes(bytes)

// ✅ Better: use slice or read directly
val slice = buffer.readBytes(length) // Zero-copy slice

If you find yourself frequently converting to/from ByteArray, consider whether you can work with the buffer directly instead.