Lee Thanh
Upload 3012 files
5641073
raw
history blame
13.8 kB
/**
* @author jdiaz5513
*/
import initTrace from "debug";
import { MAX_SEGMENT_LENGTH, NATIVE_LITTLE_ENDIAN } from "../constants";
import { NOT_IMPLEMENTED, SEG_REPLACEMENT_BUFFER_TOO_SMALL, SEG_SIZE_OVERFLOW } from "../errors";
import { Int64, Uint64 } from "../types";
import { format, padToWord } from "../util";
import { Message } from "./message";
import { Pointer } from "./pointers";
const trace = initTrace("capnp:segment");
trace("load");
export class Segment implements DataView {
buffer: ArrayBuffer;
/** The number of bytes currently allocated in the segment. */
byteLength: number;
/**
* This value should always be zero. It's only here to satisfy the DataView interface.
*
* In the future the Segment implementation (or a child class) may allow accessing the buffer from a nonzero offset,
* but that adds a lot of extra arithmetic.
*/
byteOffset: number;
readonly [Symbol.toStringTag] = "Segment" as "DataView";
readonly id: number;
readonly message: Message;
private _dv: DataView;
constructor(id: number, message: Message, buffer: ArrayBuffer, byteLength = 0) {
this.id = id;
this.message = message;
this.buffer = buffer;
this._dv = new DataView(buffer);
this.byteOffset = 0;
this.byteLength = byteLength;
}
/**
* Attempt to allocate the requested number of bytes in this segment. If this segment is full this method will return
* a pointer to freshly allocated space in another segment from the same message.
*
* @param {number} byteLength The number of bytes to allocate, will be rounded up to the nearest word.
* @returns {Pointer} A pointer to the newly allocated space.
*/
allocate(byteLength: number): Pointer {
trace("allocate(%d)", byteLength);
// eslint-disable-next-line @typescript-eslint/no-this-alias
let segment: Segment = this;
byteLength = padToWord(byteLength);
if (byteLength > MAX_SEGMENT_LENGTH - 8) {
throw new Error(format(SEG_SIZE_OVERFLOW, byteLength));
}
if (!segment.hasCapacity(byteLength)) {
segment = segment.message.allocateSegment(byteLength);
}
const byteOffset = segment.byteLength;
segment.byteLength = segment.byteLength + byteLength;
trace("Allocated %x bytes in %s (requested segment: %s).", byteLength, this, segment);
return new Pointer(segment, byteOffset);
}
/**
* Quickly copy a word (8 bytes) from `srcSegment` into this one at the given offset.
*
* @param {number} byteOffset The offset to write the word to.
* @param {Segment} srcSegment The segment to copy the word from.
* @param {number} srcByteOffset The offset from the start of `srcSegment` to copy from.
* @returns {void}
*/
copyWord(byteOffset: number, srcSegment: Segment, srcByteOffset: number): void {
const value = srcSegment._dv.getFloat64(srcByteOffset, NATIVE_LITTLE_ENDIAN);
this._dv.setFloat64(byteOffset, value, NATIVE_LITTLE_ENDIAN);
}
/**
* Quickly copy words from `srcSegment` into this one.
*
* @param {number} byteOffset The offset to start copying into.
* @param {Segment} srcSegment The segment to copy from.
* @param {number} srcByteOffset The start offset to copy from.
* @param {number} wordLength The number of words to copy.
* @returns {void}
*/
copyWords(byteOffset: number, srcSegment: Segment, srcByteOffset: number, wordLength: number): void {
const dst = new Float64Array(this.buffer, byteOffset, wordLength);
const src = new Float64Array(srcSegment.buffer, srcByteOffset, wordLength);
dst.set(src);
}
/**
* Quickly fill a number of words in the buffer with zeroes.
*
* @param {number} byteOffset The first byte to set to zero.
* @param {number} wordLength The number of words (not bytes!) to zero out.
* @returns {void}
*/
fillZeroWords(byteOffset: number, wordLength: number): void {
new Float64Array(this.buffer, byteOffset, wordLength).fill(0);
}
/** WARNING: This function is not yet implemented. */
getBigInt64(byteOffset: number, littleEndian?: boolean): bigint {
throw new Error(format(NOT_IMPLEMENTED, byteOffset, littleEndian));
}
/** WARNING: This function is not yet implemented. */
getBigUint64(byteOffset: number, littleEndian?: boolean): bigint {
throw new Error(format(NOT_IMPLEMENTED, byteOffset, littleEndian));
}
/**
* Get the total number of bytes available in this segment (the size of its underlying buffer).
*
* @returns {number} The total number of bytes this segment can hold.
*/
getCapacity(): number {
return this.buffer.byteLength;
}
/**
* Read a float32 value out of this segment.
*
* @param {number} byteOffset The offset in bytes to the value.
* @returns {number} The value.
*/
getFloat32(byteOffset: number): number {
return this._dv.getFloat32(byteOffset, true);
}
/**
* Read a float64 value out of this segment.
*
* @param {number} byteOffset The offset in bytes to the value.
* @returns {number} The value.
*/
getFloat64(byteOffset: number): number {
return this._dv.getFloat64(byteOffset, true);
}
/**
* Read an int16 value out of this segment.
*
* @param {number} byteOffset The offset in bytes to the value.
* @returns {number} The value.
*/
getInt16(byteOffset: number): number {
return this._dv.getInt16(byteOffset, true);
}
/**
* Read an int32 value out of this segment.
*
* @param {number} byteOffset The offset in bytes to the value.
* @returns {number} The value.
*/
getInt32(byteOffset: number): number {
return this._dv.getInt32(byteOffset, true);
}
/**
* Read an int64 value out of this segment.
*
* @param {number} byteOffset The offset in bytes to the value.
* @returns {number} The value.
*/
getInt64(byteOffset: number): Int64 {
return new Int64(new Uint8Array(this.buffer.slice(byteOffset, byteOffset + 8)));
}
/**
* Read an int8 value out of this segment.
*
* @param {number} byteOffset The offset in bytes to the value.
* @returns {number} The value.
*/
getInt8(byteOffset: number): number {
return this._dv.getInt8(byteOffset);
}
/**
* Read a uint16 value out of this segment.
*
* @param {number} byteOffset The offset in bytes to the value.
* @returns {number} The value.
*/
getUint16(byteOffset: number): number {
return this._dv.getUint16(byteOffset, true);
}
/**
* Read a uint32 value out of this segment.
*
* @param {number} byteOffset The offset in bytes to the value.
* @returns {number} The value.
*/
getUint32(byteOffset: number): number {
return this._dv.getUint32(byteOffset, true);
}
/**
* Read a uint8 value out of this segment.
* NOTE: this does not copy the memory region, so updates to the underlying buffer will affect the Uint64 value!
*
* @param {number} byteOffset The offset in bytes to the value.
* @returns {number} The value.
*/
getUint64(byteOffset: number): Uint64 {
return new Uint64(new Uint8Array(this.buffer.slice(byteOffset, byteOffset + 8)));
}
/**
* Read a uint8 value out of this segment.
*
* @param {number} byteOffset The offset in bytes to the value.
* @returns {number} The value.
*/
getUint8(byteOffset: number): number {
return this._dv.getUint8(byteOffset);
}
hasCapacity(byteLength: number): boolean {
trace("hasCapacity(%d)", byteLength);
// capacity - allocated >= requested
return this.buffer.byteLength - this.byteLength >= byteLength;
}
/**
* Quickly check the word at the given offset to see if it is equal to zero.
*
* PERF_V8: Fastest way to do this is by reading the whole word as a `number` (float64) in the _native_ endian format
* and see if it's zero.
*
* Benchmark: http://jsben.ch/#/Pjooc
*
* @param {number} byteOffset The offset to the word.
* @returns {boolean} `true` if the word is zero.
*/
isWordZero(byteOffset: number): boolean {
return this._dv.getFloat64(byteOffset, NATIVE_LITTLE_ENDIAN) === 0;
}
/**
* Swap out this segment's underlying buffer with a new one. It's assumed that the new buffer has the same content but
* more free space, otherwise all existing pointers to this segment will be hilariously broken.
*
* @param {ArrayBuffer} buffer The new buffer to use.
* @returns {void}
*/
replaceBuffer(buffer: ArrayBuffer): void {
trace("replaceBuffer(%p)", buffer);
if (this.buffer === buffer) return;
if (buffer.byteLength < this.byteLength) {
throw new Error(SEG_REPLACEMENT_BUFFER_TOO_SMALL);
}
this._dv = new DataView(buffer);
this.buffer = buffer;
}
/** WARNING: This function is not yet implemented. */
setBigInt64(byteOffset: number, value: bigint, littleEndian?: boolean): void {
throw new Error(format(NOT_IMPLEMENTED, byteOffset, value, littleEndian));
}
/** WARNING: This function is not yet implemented. */
setBigUint64(byteOffset: number, value: bigint, littleEndian?: boolean): void {
throw new Error(format(NOT_IMPLEMENTED, byteOffset, value, littleEndian));
}
/**
* Write a float32 value to the specified offset.
*
* @param {number} byteOffset The offset from the beginning of the buffer.
* @param {number} val The value to store.
* @returns {void}
*/
setFloat32(byteOffset: number, val: number): void {
this._dv.setFloat32(byteOffset, val, true);
}
/**
* Write an float64 value to the specified offset.
*
* @param {number} byteOffset The offset from the beginning of the buffer.
* @param {number} val The value to store.
* @returns {void}
*/
setFloat64(byteOffset: number, val: number): void {
this._dv.setFloat64(byteOffset, val, true);
}
/**
* Write an int16 value to the specified offset.
*
* @param {number} byteOffset The offset from the beginning of the buffer.
* @param {number} val The value to store.
* @returns {void}
*/
setInt16(byteOffset: number, val: number): void {
this._dv.setInt16(byteOffset, val, true);
}
/**
* Write an int32 value to the specified offset.
*
* @param {number} byteOffset The offset from the beginning of the buffer.
* @param {number} val The value to store.
* @returns {void}
*/
setInt32(byteOffset: number, val: number): void {
this._dv.setInt32(byteOffset, val, true);
}
/**
* Write an int8 value to the specified offset.
*
* @param {number} byteOffset The offset from the beginning of the buffer.
* @param {number} val The value to store.
* @returns {void}
*/
setInt8(byteOffset: number, val: number): void {
this._dv.setInt8(byteOffset, val);
}
/**
* Write an int64 value to the specified offset.
*
* @param {number} byteOffset The offset from the beginning of the buffer.
* @param {Int64} val The value to store.
* @returns {void}
*/
setInt64(byteOffset: number, val: Int64): void {
this._dv.setUint8(byteOffset, val.buffer[0]);
this._dv.setUint8(byteOffset + 1, val.buffer[1]);
this._dv.setUint8(byteOffset + 2, val.buffer[2]);
this._dv.setUint8(byteOffset + 3, val.buffer[3]);
this._dv.setUint8(byteOffset + 4, val.buffer[4]);
this._dv.setUint8(byteOffset + 5, val.buffer[5]);
this._dv.setUint8(byteOffset + 6, val.buffer[6]);
this._dv.setUint8(byteOffset + 7, val.buffer[7]);
}
/**
* Write a uint16 value to the specified offset.
*
* @param {number} byteOffset The offset from the beginning of the buffer.
* @param {number} val The value to store.
* @returns {void}
*/
setUint16(byteOffset: number, val: number): void {
this._dv.setUint16(byteOffset, val, true);
}
/**
* Write a uint32 value to the specified offset.
*
* @param {number} byteOffset The offset from the beginning of the buffer.
* @param {number} val The value to store.
* @returns {void}
*/
setUint32(byteOffset: number, val: number): void {
this._dv.setUint32(byteOffset, val, true);
}
/**
* Write a uint64 value to the specified offset.
* TODO: benchmark other ways to perform this write operation.
*
* @param {number} byteOffset The offset from the beginning of the buffer.
* @param {Uint64} val The value to store.
* @returns {void}
*/
setUint64(byteOffset: number, val: Uint64): void {
this._dv.setUint8(byteOffset + 0, val.buffer[0]);
this._dv.setUint8(byteOffset + 1, val.buffer[1]);
this._dv.setUint8(byteOffset + 2, val.buffer[2]);
this._dv.setUint8(byteOffset + 3, val.buffer[3]);
this._dv.setUint8(byteOffset + 4, val.buffer[4]);
this._dv.setUint8(byteOffset + 5, val.buffer[5]);
this._dv.setUint8(byteOffset + 6, val.buffer[6]);
this._dv.setUint8(byteOffset + 7, val.buffer[7]);
}
/**
* Write a uint8 (byte) value to the specified offset.
*
* @param {number} byteOffset The offset from the beginning of the buffer.
* @param {number} val The value to store.
* @returns {void}
*/
setUint8(byteOffset: number, val: number): void {
this._dv.setUint8(byteOffset, val);
}
/**
* Write a zero word (8 bytes) to the specified offset. This is slightly faster than calling `setUint64` or
* `setFloat64` with a zero value.
*
* Benchmark: http://jsben.ch/#/dUdPI
*
* @param {number} byteOffset The offset of the word to set to zero.
* @returns {void}
*/
setWordZero(byteOffset: number): void {
this._dv.setFloat64(byteOffset, 0, NATIVE_LITTLE_ENDIAN);
}
toString(): string {
return format(
"Segment_id:%d,off:%a,len:%a,cap:%a",
this.id,
this.byteLength,
this.byteOffset,
this.buffer.byteLength
);
}
}