capnp-js
Version:
Capnproto run-time decoding and encoding for Node
201 lines (200 loc) • 7.75 kB
JavaScript
var isNull = require('../reader/isNull');
var Reader = require('../reader/Arena');
var reader = require('../reader/layout/structure');
var wordAlign = require('../wordAlign');
var copy = require('./copy/pointer');
var builder = require('./layout/structure');
var upgrade = require('./upgrade');
var Builder = function(alloc, zero, size) {
if (size < 8) size = 8;
this.__alloc = alloc;
this.__zero = zero;
this._nextSize = size || 8192;
this._segments = [];
this._isRooted = false;
};
Builder.prototype.getSegment = function(id) {
if (id >= this._segments.length) {
throw new RangeError();
}
return this._segments[id];
};
Builder.IS_READER = Builder.prototype.IS_READER = false;
Builder.prototype.asReader = function(maxDepth, maxBytes) {
if (maxDepth === undefined) maxDepth = +Infinity;
if (maxBytes === undefined) maxBytes = +Infinity;
return new Reader(this._segments, maxDepth, maxBytes);
};
Builder.prototype.isEquivTo = function(arena) {
// Determine whether the two arenas share the same underlying segments.
return this._segments === arena._segments;
};
Builder.prototype.initRoot = function(Structure) {
var ctSize = Structure._CT.dataBytes + Structure._CT.pointersBytes;
return Structure._init(this, this._root());
};
Builder.prototype.initOrphan = function(Type, optionalLength) {
if (optionalLength === undefined) {
if (Type._CT.meta === 1) throw new Error("'initOrphan' for list types requires a length");
} else {
if (Type._CT.meta === 0) throw new Error("'initOrphan' for struct types requires no length");
}
return Type._initOrphan(this, optionalLength);
};
Builder.prototype.getRoot = function(Structure) {
var ct = Structure._CT;
var ctSize = ct.dataBytes + ct.pointersBytes;
var root = this._root();
if (isNull(root)) {
var blob = this._preallocate(root.segment, ctSize);
this._zero(blob, ctSize);
builder.preallocated(root, blob, ct);
} else {
var layout = reader.safe(this, root);
if (layout.end - layout.dataSection < ctSize) {
upgrade.structure(this, root, ct);
}
}
this._isRooted = true;
return Structure._deref(this, root);
};
Builder.prototype.setRoot = function(reader) {
if (reader._CT.meta !== 0) throw new Error("Root must be a struct");
copy.setStructPointer(reader._arena, reader._layout(), this, this._root());
this._isRooted = true;
};
Builder.prototype.adoptRoot = function(orphan) {
if (orphan._arena !== this) {
throw new Error("Adopting an orphan from some other arena");
}
if (this._isRooted) {
throw new Error("The arena already has a root.");
}
builder.nonpreallocated(this, this._root(), {
segment: orphan._segment,
position: orphan._dataSection
}, orphan._rt());
this._isRooted = true;
};
Builder.prototype._root = function() {
var s0 = this._segments[0];
if (s0 && s0._position > 0) {
return {
segment: this._segments[0],
position: 0
};
} else {
/*
* On node, `allocate` zeros the last word, so this is zeroed
* already.
*/
return this._allocate(8);
}
};
/*
* Allocate space on a segment.
*
* bytes UInt32 - Number of bytes sought.
*
* RETURNS: Datum
* * `segment` - The segment containing the allocated space.
* * `position` - The word aligned offset into `segment` where the allocated
* space begins. The "position" word choice is for generality across a
* few functions.
*/
Builder.prototype._allocate = function(bytes) {
var segment;
bytes = wordAlign(bytes);
// Greedily try to find sufficient space within `this._segments`.
for (var i = 0; i < this._segments.length; ++i) {
segment = this._segments[i];
var oldEnd = segment._position;
if (oldEnd + bytes < segment.length) {
segment._position += bytes;
// Zero-fill the last word of all allocations.
if (bytes > 0) {
segment.fill(0, segment._position - 8, segment._position);
}
return {
segment: segment,
position: oldEnd
};
}
}
// Create a new segment.
if (this._nextSize < bytes) this._nextSize = bytes;
segment = this.__alloc(this._nextSize);
// Double size for next allocation.
this._nextSize = this._nextSize << 1;
segment._id = this._segments.length;
segment._position = bytes;
this._segments.push(segment);
// Zero-fill the last word of all allocations.
segment.fill(0, bytes - 8, bytes);
return {
segment: segment,
position: 0
};
};
/*
* Allocate a contiguous blob of memory of length `bytes` if available on
* `localSegment`. Allocate a length of `bytes+8` on any segment if length
* `bytes` was unavailable on `localSegment`. In the latter case, the
* resulting datum will have its position set such that the 8 extra bytes
* are located immediately prior: 00 00 00 00 00 00 00 00 (datum) 00*bytes.
*
* * localSegment Uint8Array - Existing blob to bias allocation toward.
* * bytes UInt32 - Length of the sought memory blob.
*
* * RETURNS: Datum - Position of the allocated memory blob. This blob is
* preceded by 8 bytes if the sought blob size could not be allocated on
* `localSegment`.
*/
Builder.prototype._preallocate = function(localSegment, bytes) {
bytes = wordAlign(bytes);
var oldEnd = localSegment._position;
if (oldEnd + bytes <= localSegment.length) {
localSegment._position += bytes;
/*
* Zero-fill the last word of all allocations so that subword lists
* do not contain junk at their ends.
*/
if (bytes > 0) {
localSegment.fill(0, localSegment._position - 8, localSegment._position);
}
return {
segment: localSegment,
position: oldEnd
};
}
/*
* Provide leading space for a far pointer landing pad if there's
* insufficient space on `localSegment`.
*/
var datum = this._allocate(bytes + 8);
datum.position += 8;
return datum;
};
/*
* Copy `length` bytes from `source` to `target`.
*
* source Datum
* length UInt32
* target Datum
*/
Builder.prototype._write = function(source, length, target) {
source.segment.copy(target.segment, target.position, source.position, source.position + length);
};
Builder.prototype._zero = function(pointer, length) {
this.__zero(pointer.segment, pointer.position, length);
};
Builder.prototype._allocateOrphan = function(bytes) {
var s0 = this._segments[0];
if (s0 && s0._position > 0) return this._allocate(bytes); else {
// Leave space at head for the root pointer.
var blob = this._allocate(bytes + 8);
blob.position += 8;
return blob;
}
};
module.exports = Builder;