import crypto from "crypto";
/**
* NOTE(bowei): we use a hash function that is NOT md5 -
* Either https://github.com/sublee/squirrel3-python/blob/master/squirrel3.py or https://github.com/svaarala/duktape/blob/master/misc/splitmix64.c works fine and is much faster
* Reference: https://www.youtube.com/watch?v=e4b--cyXEsM or https://www.youtube.com/watch?v=LWFzPP8ZbdU
* TODO(bowei): port bigint to wasm for faster 64bit operations
*/
// NOTE(bowei): untested
export function splitmix64(seed: bigint, i: bigint) {
let z: bigint = seed + i * BigInt("0x9e3779b97f4a7c15");
z = ( z ^ ( z >> BigInt(30) ) ) * BigInt("0xBF58476D1CE4E5B9");
z = ( z ^ ( z >> BigInt(27) ) ) * BigInt(0x94D049BB133111EB);
return z ^ ( z >> BigInt(31) );
}
export const INTMAX32 = 2 ** 32;
export function squirrel3(i: number) {
let n = (i + INTMAX32) % INTMAX32;
n = Math.imul(n, 0xb5297a4d);
n ^= n >>> 8;
n += 0x68e31da4;
n ^= n << 8;
n = Math.imul(n, 0x1b56c4e9);
n ^= n >>> 8;
return (n + INTMAX32) % INTMAX32;
}
export const PRIME32 = 0x3233f2cd; // not used ; useful for hashing integers; a 32 bit prime
/**
* Md5 is 16 bytes, or max int of 256 ** 16 = 2 ** 128
*/
export class HashState {
private seed!: Buffer;
/**
* HashState().step("foo") is equivalent to HashState("foo")
*/
constructor(seed?: string) {
const buffer = crypto
.createHash("md5")
.update((seed || "").toString())
.digest();
this.seed = buffer;
}
public peekRandom(): number {
const buffer = crypto.createHash("md5").update(this.seed).digest();
return Number(this.bufferToBigInt(buffer) % BigInt(2 ** 32)) % 2 ** 32;
}
// increment the seed linearly by 1
public step(numSteps: number = 1) {
this.seed = this.bigIntToBuffer(this.bufferToBigInt(this.seed) + BigInt(1));
}
public stepSeed(seed: string) {
const buffer = crypto.createHash("md5").update(seed.toString()).digest();
this.seed = this.bigIntToBuffer(
this.bufferToBigInt(this.seed) + this.bufferToBigInt(buffer)
);
}
private bigIntToBuffer(b: bigint): Buffer {
let buf = Buffer.alloc(16);
let val = b;
for (let i = 0; i < 16; i++) {
buf[i] = Number(val % BigInt(256));
val = val / BigInt(256);
}
return buf;
}
private bufferToBigInt(b: Buffer): bigint {
let val = BigInt(0);
for (let i = 0; i < 16; i++) {
val = val * BigInt(256) + BigInt(b[i]);
}
return val;
}
public random(): number {
this.step();
return this.peekRandom();
}
}