Merge branch 'sock_encoding' into atree_encode
This commit is contained in:
commit
7e40d6a926
1 changed files with 263 additions and 23 deletions
286
js/utils.js
286
js/utils.js
|
@ -74,8 +74,10 @@ function log(b, n) {
|
|||
// https://stackoverflow.com/a/27696695
|
||||
// Modified for fixed precision
|
||||
|
||||
// Base64.fromInt(-2147483648); // gives "200000"
|
||||
// Base64.toInt("200000"); // gives -2147483648
|
||||
Base64 = (function () {
|
||||
var digitsStr =
|
||||
var digitsStr =
|
||||
// 0 8 16 24 32 40 48 56 63
|
||||
// v v v v v v v v v
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-";
|
||||
|
@ -125,8 +127,246 @@ Base64 = (function () {
|
|||
};
|
||||
})();
|
||||
|
||||
// Base64.fromInt(-2147483648); // gives "200000"
|
||||
// Base64.toInt("200000"); // gives -2147483648
|
||||
|
||||
/** A class used to represent an arbitrary length bit vector. Very useful for encoding and decoding.
|
||||
*
|
||||
*/
|
||||
class BitVector {
|
||||
|
||||
/** Constructs an arbitrary-length bit vector.
|
||||
* @class
|
||||
* @param {String | Number} data - The data to append.
|
||||
* @param {Number} length - A set length for the data. Ignored if data is a string.
|
||||
*
|
||||
* The structure of the Uint32Array should be [[last, ..., first], ..., [last, ..., first], [empty space, last, ..., first]]
|
||||
*/
|
||||
constructor(data, length) {
|
||||
let bit_vec = [];
|
||||
|
||||
if (typeof data === "string") {
|
||||
let int = 0;
|
||||
let bv_idx = 0;
|
||||
length = data.length * 6;
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let char = Base64.toInt(data[i]);
|
||||
let pre_pos = bv_idx % 32;
|
||||
int |= (char << bv_idx);
|
||||
bv_idx += 6;
|
||||
let post_pos = bv_idx % 32;
|
||||
if (post_pos < pre_pos) { //we have to have filled up the integer
|
||||
bit_vec.push(int);
|
||||
int = (char >>> (6 - post_pos));
|
||||
}
|
||||
|
||||
if (i == data.length - 1 && post_pos != 0) {
|
||||
bit_vec.push(int);
|
||||
}
|
||||
}
|
||||
} else if (typeof data === "number") {
|
||||
if (typeof length === "undefined")
|
||||
if (length < 0) {
|
||||
throw new RangeError("BitVector must have nonnegative length.");
|
||||
}
|
||||
|
||||
//convert to int just in case
|
||||
data = Math.round(data);
|
||||
|
||||
//range of numbers that won't fit in a uint32
|
||||
if (data > 2**32 - 1 || data < -(2 ** 32 - 1)) {
|
||||
throw new RangeError("Numerical data has to fit within a 32-bit integer range to instantiate a BitVector.");
|
||||
}
|
||||
bit_vec.push(data);
|
||||
} else {
|
||||
throw new TypeError("BitVector must be instantiated with a Number or a B64 String");
|
||||
}
|
||||
|
||||
this.length = length;
|
||||
this.bits = new Uint32Array(bit_vec);
|
||||
}
|
||||
|
||||
/** Return value of bit at index idx.
|
||||
*
|
||||
* @param {Number} idx - The index to read
|
||||
*
|
||||
* @returns The bit value at position idx
|
||||
*/
|
||||
read_bit(idx) {
|
||||
if (idx < 0 || idx >= this.length) {
|
||||
throw new RangeError("Cannot read bit outside the range of the BitVector.");
|
||||
}
|
||||
return ((this.bits[Math.floor(idx / 32)] & (1 << (idx % 32))) == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
/** Returns an integer value (if possible) made from the range of bits [start, end). Undefined behavior if the range to read is too big.
|
||||
*
|
||||
* @param {Number} start - The index to start slicing from. Inclusive.
|
||||
* @param {Number} end - The index to end slicing at. Exclusive.
|
||||
*
|
||||
* @returns An integer representation of the sliced bits.
|
||||
*/
|
||||
slice(start, end) {
|
||||
//TO NOTE: JS shifting is ALWAYS in mod 32. a << b will do a << (b mod 32) implicitly.
|
||||
|
||||
if (end < start) {
|
||||
throw new RangeError("Cannot slice a range where the end is before the start.");
|
||||
} else if (end == start) {
|
||||
return 0;
|
||||
} else if (end - start > 32) {
|
||||
//requesting a slice of longer than 32 bits (safe integer "length")
|
||||
throw new RangeError("Cannot slice a range of longer than 32 bits (unsafe to store in an integer).");
|
||||
}
|
||||
|
||||
let res = 0;
|
||||
if (Math.floor((end - 1) / 32) == Math.floor(start / 32)) {
|
||||
//the range is within 1 uint32 section - do some relatively fast bit twiddling
|
||||
res = (this.bits[Math.floor(start / 32)] & ~((((~0) << ((end - 1))) << 1) | ~((~0) << (start)))) >>> (start % 32);
|
||||
} else {
|
||||
//the number of bits in the uint32s
|
||||
let start_pos = (start % 32);
|
||||
let int_idx = Math.floor(start/32);
|
||||
res = (this.bits[int_idx] & ((~0) << (start))) >>> (start_pos);
|
||||
res |= (this.bits[int_idx + 1] & ~((~0) << (end))) << (32 - start_pos);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
// General code - slow
|
||||
// for (let i = start; i < end; i++) {
|
||||
// res |= (get_bit(i) << (i - start));
|
||||
// }
|
||||
}
|
||||
|
||||
/** Assign bit at index idx to 1.
|
||||
*
|
||||
* @param {Number} idx - The index to set.
|
||||
*/
|
||||
set_bit(idx) {
|
||||
if (idx < 0 || idx >= this.length) {
|
||||
throw new RangeError("Cannot set bit outside the range of the BitVector.");
|
||||
}
|
||||
this.bits[Math.floor(idx / 32)] |= (1 << idx % 32);
|
||||
}
|
||||
|
||||
/** Assign bit at index idx to 0.
|
||||
*
|
||||
* @param {Number} idx - The index to clear.
|
||||
*/
|
||||
clear_bit(idx) {
|
||||
if (idx < 0 || idx >= this.length) {
|
||||
throw new RangeError("Cannot clear bit outside the range of the BitVector.");
|
||||
}
|
||||
this.bits[Math.floor(idx / 32)] &= ~(1 << idx % 32);
|
||||
}
|
||||
|
||||
/** Creates a string version of the bit vector in B64. Does not keep the order of elements a sensible human readable format.
|
||||
*
|
||||
* @returns A b64 string representation of the BitVector.
|
||||
*/
|
||||
toB64() {
|
||||
if (this.length == 0) {
|
||||
return "";
|
||||
}
|
||||
let b64_str = "";
|
||||
let i = 0;
|
||||
while (i < this.length) {
|
||||
b64_str += Base64.fromIntV(this.slice(i, i + 6), 1);
|
||||
i += 6;
|
||||
}
|
||||
|
||||
return b64_str;
|
||||
}
|
||||
|
||||
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
|
||||
*
|
||||
* @returns A bit string representation of the BitVector. Goes from higher-indexed bits to lower-indexed bits. (n ... 0)
|
||||
*/
|
||||
toString() {
|
||||
let ret_str = "";
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
ret_str = (this.read_bit(i) == 0 ? "0": "1") + ret_str;
|
||||
}
|
||||
return ret_str;
|
||||
}
|
||||
|
||||
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
|
||||
*
|
||||
* @returns A bit string representation of the BitVector. Goes from lower-indexed bits to higher-indexed bits. (0 ... n)
|
||||
*/
|
||||
toStringR() {
|
||||
let ret_str = "";
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
ret_str += (this.read_bit(i) == 0 ? "0": "1");
|
||||
}
|
||||
return ret_str;
|
||||
}
|
||||
|
||||
/** Appends data to the BitVector.
|
||||
*
|
||||
* @param {Number | String} data - The data to append.
|
||||
* @param {Number} length - The length, in bits, of the new data. This is ignored if data is a string.
|
||||
*/
|
||||
append(data, length) {
|
||||
if (length < 0) {
|
||||
throw new RangeError("BitVector length must increase by a nonnegative number.");
|
||||
}
|
||||
|
||||
let bit_vec = [];
|
||||
for (const uint of this.bits) {
|
||||
bit_vec.push(uint);
|
||||
}
|
||||
if (typeof data === "string") {
|
||||
let int = bit_vec[bit_vec.length - 1];
|
||||
let bv_idx = this.length;
|
||||
length = data.length * 6;
|
||||
let updated_curr = false;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let char = Base64.toInt(data[i]);
|
||||
let pre_pos = bv_idx % 32;
|
||||
int |= (char << bv_idx);
|
||||
bv_idx += 6;
|
||||
let post_pos = bv_idx % 32;
|
||||
if (post_pos < pre_pos) { //we have to have filled up the integer
|
||||
if (bit_vec.length == this.bits.length && !updated_curr) {
|
||||
bit_vec[bit_vec.length - 1] = int;
|
||||
updated_curr = true;
|
||||
} else {
|
||||
bit_vec.push(int);
|
||||
}
|
||||
int = (char >>> (6 - post_pos));
|
||||
}
|
||||
|
||||
if (i == data.length - 1) {
|
||||
if (bit_vec.length == this.bits.length && !updated_curr) {
|
||||
bit_vec[bit_vec.length - 1] = int;
|
||||
} else if (post_pos != 0) {
|
||||
bit_vec.push(int);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (typeof data === "number") {
|
||||
//convert to int just in case
|
||||
let int = Math.round(data);
|
||||
|
||||
//range of numbers that "could" fit in a uint32 -> [0, 2^32) U [-2^31, 2^31)
|
||||
if (data > 2**32 - 1 || data < -(2 ** 31)) {
|
||||
throw new RangeError("Numerical data has to fit within a 32-bit integer range to instantiate a BitVector.");
|
||||
}
|
||||
//could be split between multiple new ints
|
||||
//reminder that shifts implicitly mod 32
|
||||
bit_vec[bit_vec.length - 1] |= ((int & ~((~0) << length)) << (this.length));
|
||||
if (((this.length - 1) % 32 + 1) + length > 32) {
|
||||
bit_vec.push(int >>> (32 - this.length));
|
||||
}
|
||||
} else {
|
||||
throw new TypeError("BitVector must be appended with a Number or a B64 String");
|
||||
}
|
||||
|
||||
this.bits = new Uint32Array(bit_vec);
|
||||
this.length += length;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Turns a raw stat and a % stat into a final stat on the basis that - raw and >= 100% becomes 0 and + raw and <=-100% becomes negative.
|
||||
|
@ -238,7 +478,7 @@ function randomColor() {
|
|||
|
||||
/**
|
||||
* Generates a random color, but lightning must be relatively high (>0.5).
|
||||
*
|
||||
*
|
||||
* @returns a random color in RGB 6-bit form.
|
||||
*/
|
||||
function randomColorLight() {
|
||||
|
@ -246,7 +486,7 @@ function randomColorLight() {
|
|||
}
|
||||
|
||||
/** Generates a random color given HSL restrictions.
|
||||
*
|
||||
*
|
||||
* @returns a random color in RGB 6-bit form.
|
||||
*/
|
||||
function randomColorHSL(h,s,l) {
|
||||
|
@ -298,8 +538,8 @@ function randomColorHSL(h,s,l) {
|
|||
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
||||
}
|
||||
|
||||
/** Creates a tooltip.
|
||||
*
|
||||
/** Creates a tooltip.
|
||||
*
|
||||
* @param {DOM Element} elem - the element to make a tooltip
|
||||
* @param {String} element_type - the HTML element type that the tooltiptext should be.
|
||||
* @param {String} tooltiptext - the text to display in the tooltip.
|
||||
|
@ -330,7 +570,7 @@ function createTooltip(elem, element_type, tooltiptext, parent, classList) {
|
|||
}
|
||||
|
||||
/** A generic function that toggles the on and off state of a button.
|
||||
*
|
||||
*
|
||||
* @param {String} button_id - the id name of the button.
|
||||
*/
|
||||
function toggleButton(button_id) {
|
||||
|
@ -374,8 +614,8 @@ function addClasses(elem, classes) {
|
|||
return elem;
|
||||
}
|
||||
|
||||
/** A utility function that reloads the page forcefully.
|
||||
*
|
||||
/** A utility function that reloads the page forcefully.
|
||||
*
|
||||
*/
|
||||
async function hardReload() {
|
||||
//https://gist.github.com/rmehner/b9a41d9f659c9b1c3340
|
||||
|
@ -413,13 +653,13 @@ const getScript = url => new Promise((resolve, reject) => {
|
|||
document.head.appendChild(script);
|
||||
})
|
||||
|
||||
/*
|
||||
/*
|
||||
GENERIC TEST FUNCTIONS
|
||||
*/
|
||||
/** The generic assert function. Fails on all "false-y" values. Useful for non-object equality checks, boolean value checks, and existence checks.
|
||||
*
|
||||
*
|
||||
* @param {*} arg - argument to assert.
|
||||
* @param {String} msg - the error message to throw.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert(arg, msg) {
|
||||
if (!arg) {
|
||||
|
@ -428,10 +668,10 @@ GENERIC TEST FUNCTIONS
|
|||
}
|
||||
|
||||
/** Asserts object equality of the 2 parameters. For loose and strict asserts, use assert().
|
||||
*
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {String} msg - the error message to throw.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_equals(arg1, arg2, msg) {
|
||||
if (!Object.is(arg1, arg2)) {
|
||||
|
@ -440,10 +680,10 @@ function assert_equals(arg1, arg2, msg) {
|
|||
}
|
||||
|
||||
/** Asserts object inequality of the 2 parameters. For loose and strict asserts, use assert().
|
||||
*
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {String} msg - the error message to throw.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_not_equals(arg1, arg2, msg) {
|
||||
if (Object.is(arg1, arg2)) {
|
||||
|
@ -452,11 +692,11 @@ function assert_equals(arg1, arg2, msg) {
|
|||
}
|
||||
|
||||
/** Asserts proximity between 2 arguments. Should be used for any floating point datatype.
|
||||
*
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {Number} epsilon - the margin of error (<= del difference is ok). Defaults to -1E5.
|
||||
* @param {String} msg - the error message to throw.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_near(arg1, arg2, epsilon = 1E-5, msg) {
|
||||
if (Math.abs(arg1 - arg2) > epsilon) {
|
||||
|
@ -465,7 +705,7 @@ function assert_near(arg1, arg2, epsilon = 1E-5, msg) {
|
|||
}
|
||||
|
||||
/** Asserts that the input argument is null.
|
||||
*
|
||||
*
|
||||
* @param {*} arg - the argument to test for null.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
|
@ -476,7 +716,7 @@ function assert_null(arg, msg) {
|
|||
}
|
||||
|
||||
/** Asserts that the input argument is undefined.
|
||||
*
|
||||
*
|
||||
* @param {*} arg - the argument to test for undefined.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
|
@ -487,7 +727,7 @@ function assert_null(arg, msg) {
|
|||
}
|
||||
|
||||
/** Asserts that there is an error when a callback function is run.
|
||||
*
|
||||
*
|
||||
* @param {Function} func_binding - a function binding to run. Can be passed in with func.bind(null, arg1, ..., argn)
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
|
@ -496,7 +736,7 @@ function assert_error(func_binding, msg) {
|
|||
func_binding();
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error(msg ? msg : "Function didn't throw an error.");
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue