From 77a3ff935d7cbc8528ae5be19e7e6a5c7126b44d Mon Sep 17 00:00:00 2001 From: Michael Green Date: Thu, 20 Nov 2025 13:10:41 +1100 Subject: [PATCH] Add utility functions for hashing and integrate into cache management --- data/loader.js | 1 + data/src/cache.js | 13 +++++++++++++ data/src/emulator.js | 44 ++++---------------------------------------- data/src/utils.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 40 deletions(-) create mode 100644 data/src/utils.js diff --git a/data/loader.js b/data/loader.js index 0bf789c..36769ff 100644 --- a/data/loader.js +++ b/data/loader.js @@ -4,6 +4,7 @@ "nipplejs.js", "shaders.js", "storage.js", + "utils.js", "cache.js", "gamepad.js", "GameManager.js", diff --git a/data/src/cache.js b/data/src/cache.js index b2e876c..52b2d2f 100644 --- a/data/src/cache.js +++ b/data/src/cache.js @@ -20,6 +20,8 @@ class EJS_Cache { this.minAgeMins = Math.max(60, maxAgeMins * 0.1); // Minimum 1 hour, or 10% of max age this.debug = debug; + this.utils = new EJS_UTILS(); + if (this.debug) { console.log("Initialized EJS_Cache with settings:", { enabled: this.enabled, @@ -33,6 +35,17 @@ class EJS_Cache { } } + /** + * Generates a cache key for the given data array. + * @param {Uint8Array} dataArray + * @returns {string} The generated cache key. + */ + generateCacheKey(dataArray) { + let hash = this.utils.simpleHash(dataArray); + const compressionCacheKey = "Obj-" + hash + "-" + dataArray.length; + return compressionCacheKey; + } + /** * Retrieves an item from the cache. * @param {*} key - The unique key identifying the cached item. diff --git a/data/src/emulator.js b/data/src/emulator.js index 6b96bcc..3e490c3 100644 --- a/data/src/emulator.js +++ b/data/src/emulator.js @@ -209,6 +209,7 @@ class EmulatorJS { this.checkForUpdates(); } this.netplayEnabled = true; + this.utils = new EJS_UTILS(); this.config = config; this.config.buttonOpts = this.buildButtonOptions(this.config.buttonOpts); this.config.settingsLanguage = window.EJS_settingsLanguage || false; @@ -579,11 +580,7 @@ class EmulatorJS { // Generate cache key based on data hash const hashStartTime = performance.now(); const dataArray = new Uint8Array(data); - let hash = 0; - for (let i = 0; i < dataArray.length; i++) { - hash = ((hash << 5) - hash + dataArray[i]) & 0xffffffff; - } - const cacheKey = `compression_${hash}_${dataArray.length}`; + const cacheKey = this.storageCache.generateCacheKey(dataArray); const hashTime = performance.now() - hashStartTime; // Check if decompressed content is in cache @@ -763,16 +760,6 @@ class EmulatorJS { }); } - // Helper function to generate cache key for core data - const generateCoreDataCacheKey = (data) => { - const dataArray = new Uint8Array(data); - let hash = 0; - for (let i = 0; i < dataArray.length; i++) { - hash = ((hash << 5) - hash + dataArray[i]) & 0xffffffff; - } - return "compression_" + hash + "_" + dataArray.length; - }; - // Helper function to check if cached core is expired const isCoreExpired = (cachedItem) => { if (!cachedItem) return true; @@ -842,11 +829,7 @@ class EmulatorJS { if (quickDownload && quickDownload.data) { // Generate cache key the same way checkCompression does const dataArray = new Uint8Array(quickDownload.data); - let hash = 0; - for (let i = 0; i < dataArray.length; i++) { - hash = ((hash << 5) - hash + dataArray[i]) & 0xffffffff; - } - const compressionCacheKey = `compression_${hash}_${dataArray.length}`; + const compressionCacheKey = this.storageCache.generateCacheKey(dataArray); const cachedDecompression = await this.storageCache.get(compressionCacheKey); if (cachedDecompression && cachedDecompression.files && cachedDecompression.files.length > 0) { @@ -7452,28 +7435,9 @@ class EmulatorJS { } enableSaveUpdateEvent() { - // https://stackoverflow.com/questions/7616461 - // Modified to accept a buffer instead of a string and return hex instead of an int - async function cyrb53(charBuffer, seed = 0) { - let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; - for(let i = 0, ch; i < charBuffer.length; i++) { - ch = charBuffer[i]; - h1 = Math.imul(h1 ^ ch, 2654435761); - h2 = Math.imul(h2 ^ ch, 1597334677); - } - h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); - h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); - h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); - h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); - - // Cyrb53 is a 53-bit hash; we need 14 hex characters to represent it, and the first char will - // always be 0 or 1 (since it is only 1 bit) - return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(14, "0"); - }; - function withGameSaveHash(saveFile, callback) { if (saveFile) { - cyrb53(saveFile).then(digest => callback(digest, saveFile)); + this.utils.cyrb53(saveFile).then(digest => callback(digest, saveFile)); } else { console.warn("Save file not found when attempting to hash"); callback(null, null); diff --git a/data/src/utils.js b/data/src/utils.js new file mode 100644 index 0000000..3f04852 --- /dev/null +++ b/data/src/utils.js @@ -0,0 +1,42 @@ +/** + * EJS Utility Functions + */ +class EJS_UTILS { + /** + * Computes a simple hash of the given data array. + * @param {Uint8Array} dataArray + * @returns {number} The computed hash. + */ + simpleHash(dataArray) { + let hash = 0; + for (let i = 0; i < dataArray.length; i++) { + hash = ((hash << 5) - hash + dataArray[i]) & 0xffffffff; + } + return hash; + } + + /** + * Cyrb53 hash function adapted for buffers. + * @param {*} charBuffer + * @param {*} seed + * @returns {string} Hexadecimal representation of the hash. + */ + async cyrb53(charBuffer, seed = 0) { + // https://stackoverflow.com/questions/7616461 + // Modified to accept a buffer instead of a string and return hex instead of an int + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for(let i = 0, ch; i < charBuffer.length; i++) { + ch = charBuffer[i]; + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); + h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); + + // Cyrb53 is a 53-bit hash; we need 14 hex characters to represent it, and the first char will + // always be 0 or 1 (since it is only 1 bit) + return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(14, "0"); + }; +} \ No newline at end of file