prismarine-chunk
Version:
A class to hold chunk data for prismarine
162 lines (140 loc) • 4.51 kB
JavaScript
// const assert = require('assert')
const neededBits = require('./neededBits')
class BitArray {
constructor (options) {
if (options === null) {
return
}
// assert(options.bitsPerValue > 0, 'bits per value must at least 1')
// assert(options.bitsPerValue <= 32, 'bits per value exceeds 32')
if (!options.data) {
options.data = new Uint32Array(Math.ceil((options.capacity * options.bitsPerValue) / 64) * 2)
}
const valueMask = (1 << options.bitsPerValue) - 1
this.data = options.data.buffer ? new Uint32Array(options.data.buffer) : Uint32Array.from(options.data)
this.capacity = options.capacity
this.bitsPerValue = options.bitsPerValue
this.valueMask = valueMask
}
toJson () {
return JSON.stringify({
data: Array.from(this.data),
capacity: this.capacity,
bitsPerValue: this.bitsPerValue,
valueMask: this.valueMask
})
}
static fromJson (j) {
return new BitArray(JSON.parse(j))
}
toArray () {
const array = []
for (let i = 0; i < this.capacity; i++) {
array.push(this.get(i))
}
return array
}
static fromArray (array, bitsPerValue) {
const data = []
let i = 0
let curLong = 0
let curBit = 0
while (i < array.length) {
curLong |= array[i] << curBit
curBit += bitsPerValue
if (curBit > 32) {
data.push(curLong & 0xffffffff)
curBit -= 32
curLong = array[i] >>> (bitsPerValue - curBit)
}
i++
}
if (curBit > 0) {
data.push(curLong)
}
const bitarray = new BitArray(null)
bitarray.data = data
bitarray.capacity = array.length
bitarray.bitsPerValue = bitsPerValue
bitarray.valueMask = (1 << bitsPerValue) - 1
return bitarray
}
get (index) {
// assert(index >= 0 && index < this.capacity, 'index is out of bounds')
const bitIndex = index * this.bitsPerValue
const startLongIndex = bitIndex >>> 5
const startLong = this.data[startLongIndex]
const indexInStartLong = bitIndex & 31
let result = startLong >>> indexInStartLong
const endBitOffset = indexInStartLong + this.bitsPerValue
if (endBitOffset > 32) {
// Value stretches across multiple longs
const endLong = this.data[startLongIndex + 1]
result |= endLong << (32 - indexInStartLong)
}
return result & this.valueMask
}
set (index, value) {
// assert(index >= 0 && index < this.capacity, 'index is out of bounds')
// assert(value <= this.valueMask, 'value does not fit into bits per value')
const bitIndex = index * this.bitsPerValue
const startLongIndex = bitIndex >>> 5
const indexInStartLong = bitIndex & 31
// Clear bits of this value first
this.data[startLongIndex] =
((this.data[startLongIndex] & ~(this.valueMask << indexInStartLong)) |
((value & this.valueMask) << indexInStartLong)) >>> 0
const endBitOffset = indexInStartLong + this.bitsPerValue
if (endBitOffset > 32) {
// Value stretches across multiple longs
this.data[startLongIndex + 1] =
((this.data[startLongIndex + 1] &
~((1 << (endBitOffset - 32)) - 1)) |
(value >> (32 - indexInStartLong))) >>> 0
}
}
resizeTo (newBitsPerValue) {
// assert(newBitsPerValue > 0, 'bits per value must at least 1')
// assert(newBitsPerValue <= 32, 'bits per value exceeds 32')
const newArr = new BitArray({
bitsPerValue: newBitsPerValue,
capacity: this.capacity
})
for (let i = 0; i < this.capacity; ++i) {
const value = this.get(i)
if (neededBits(value) > newBitsPerValue) {
throw new Error(
"existing value in BitArray can't fit in new bits per value"
)
}
newArr.set(i, value)
}
return newArr
}
length () {
return this.data.length / 2
}
readBuffer (smartBuffer, size = this.data.length) {
if (size !== this.data.length) {
this.data = new Uint32Array(size)
this.readOffset = size * 4
return
}
for (let i = 0; i < this.data.length; i += 2) {
this.data[i + 1] = smartBuffer.readUInt32BE()
this.data[i] = smartBuffer.readUInt32BE()
}
return this
}
writeBuffer (smartBuffer) {
for (let i = 0; i < this.data.length; i += 2) {
smartBuffer.writeUInt32BE(this.data[i + 1])
smartBuffer.writeUInt32BE(this.data[i])
}
return this
}
getBitsPerValue () {
return this.bitsPerValue
}
}
module.exports = BitArray