Skip to content

Instantly share code, notes, and snippets.

@Pho3nixHun
Created October 1, 2024 03:15
Show Gist options
  • Save Pho3nixHun/8941b09fe5623f8df0255b9b51df7858 to your computer and use it in GitHub Desktop.
Save Pho3nixHun/8941b09fe5623f8df0255b9b51df7858 to your computer and use it in GitHub Desktop.
Utils
// CRC32 lookup table
export const crc32LookupTable = Array.from({ length: 256 }, (_, n) =>
Array.from({ length: 8 }).reduce(
(c) => (c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1),
n
)
);
// Calculate the CRC32 checksum on the main thread
export const crc32ArrayBuffer = (uint8Array) =>
uint8Array.reduce(
(crc, byte) => (crc >>> 8) ^ crc32LookupTable[(crc ^ byte) & 0xff],
0xffffffff
) ^
(0xffffffff >>> 0);
/**
* Generate CRC32 checksum of a Blob in the main thread.
*
* @param {Blob} blob - The Blob object to generate the checksum for.
* @returns {Promise<string>} - The CRC32 checksum as a hex string.
*/
export const generateBlobChecksum = async (blob) => {
const arrayBuffer = await blob.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
return crc32ArrayBuffer(uint8Array).toString(16);
};
/**
* @type {Worker|null} Web Worker instance, initially null until the worker is created.
*/
let worker = null;
/**
* Check if the worker is running.
* @returns {worker is Worker} - Whether the worker is currently running.
*/
export const checkWorker = () => worker instanceof Worker;
/**
* Start the Web Worker.
* @param {Object} [options={}] - Options to control worker behavior.
* @param {number} [options.chunkSize=1MB] - The chunk size (in bytes) to process the blob.
* Smaller chunk sizes reduce memory usage but increase the number of iterations.
* Common values (powers of two):
* - 512 KB = 2**19
* - 1 MB = 2**20
* - 2 MB = 2**21
* @returns {void}
*/
export const startWorker = ({chunkSize = 2**21) => {
if (!worker) {
// Create the worker inline
const workerBlob = new Blob(
[
`
self.crc32LookupTable = ${crc32LookupTable.toString()};
const crc32ArrayBuffer = (uint8Array, crc = 0xFFFFFFFF) =>
uint8Array.reduce(
(crc, byte) => (crc >>> 8) ^ crc32LookupTable[(crc ^ byte) & 0xFF],
crc
);
const processChunk = async (blob, start, chunkSize, crc) => {
const chunk = blob.slice(start, start + chunkSize);
const arrayBuffer = await chunk.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
return crc32ArrayBuffer(uint8Array, crc);
};
self.onmessage = async event => {
const blob = event.data;
const chunkSize = ${chunkSize}; // Read in 64MB chunks (adjust as needed)
let crc = 0xFFFFFFFF;
let offset = 0;
while (offset < blob.size) {
crc = await processChunk(blob, offset, chunkSize, crc);
offset += chunkSize;
}
// Finalize CRC32 checksum
crc = crc ^ 0xFFFFFFFF;
self.postMessage(crc.toString(16));
};
`,
],
{ type: "application/javascript" }
);
const workerURL = URL.createObjectURL(workerBlob);
worker = new Worker(workerURL);
return worker;
}
};
/**
* Stop the Web Worker.
* @returns {void}
* @throws {Error} If there is no worker to stop.
*/
export const stopWorker = () => {
if (checkWorker()) {
throw new Error("No running worker found");
}
worker.terminate();
worker = null;
return;
};
/**
* Generate CRC32 checksum of a Blob in a Web Worker.
*
* @param {Blob} blob - The Blob object to generate the checksum for.
* @param {Object} [options={}] - Options to control worker start and stop behavior.
* @param {boolean} [options.start=true] - Whether to start the worker if it is not already running.
* @param {boolean} [options.stop] - Whether to stop the worker after the operation. If undefined, it depends on whether the worker was started by this function call.
* @returns {Promise<string>} - A Promise that resolves with the CRC32 checksum.
*/
export const generateBlobChecksumWithWorker = async (
blob,
{ start = true, stop } = {}
) => {
const workerStartedHere = start && !checkWorker();
const stopWorkerIfNeeded = () =>
stop === true ||
(stop === undefined && workerStartedHere) && checkWorker() && stopWorker();
// Check if we need to start the worker
if (workerStartedHere) {
await startWorker(); // Ensure the worker is ready
}
return new Promise((resolve, reject) => {
// Set up event listener for the result
worker.onmessage = (event) => {
resolve(event.data); // Resolve the promise with the checksum
stopWorkerIfNeeded();
};
worker.onerror = (error) => {
reject(error); // Reject the promise if an error occurs
stopWorkerIfNeeded();
};
// Send the Blob to the worker for processing
worker.postMessage(blob);
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment