From 123bc83640c1760352ce4b4ce11ac27a3f6387fd Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Fri, 6 Jun 2025 21:13:00 +0700 Subject: [PATCH] feat(lib/assets): improve caching --- lib/assets/src/_internal.ts | 3 +++ lib/assets/src/caches.ts | 49 ++++++++++++++++--------------------- lib/assets/src/preinit.ts | 32 ++++++++++++++++-------- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/lib/assets/src/_internal.ts b/lib/assets/src/_internal.ts index 7d0ad20..f66a4c4 100644 --- a/lib/assets/src/_internal.ts +++ b/lib/assets/src/_internal.ts @@ -1,6 +1,9 @@ +import { createLogger } from '@revenge-mod/utils/logger' import type { Metro } from '@revenge-mod/modules/types' import type { Asset, AssetId } from './types' +export const _logger = createLogger('revenge.assets') + export const _customs = new Set() export const _overrides = new WeakMap() export const _metas = new WeakMap< diff --git a/lib/assets/src/caches.ts b/lib/assets/src/caches.ts index 6a02ec1..6190ace 100644 --- a/lib/assets/src/caches.ts +++ b/lib/assets/src/caches.ts @@ -1,59 +1,52 @@ // TODO(lib/assets): do not depend on Discord's ClientInfoModule for versioning, use native interop import { ClientInfoModule } from '@revenge-mod/discord/native' import { getStorage } from '@revenge-mod/storage' -// import { debounce } from '@revenge-mod/utils/callbacks' -import { createLogger } from '@revenge-mod/utils/logger' +import { debounce } from '@revenge-mod/utils/callbacks' import { mergeDeep } from '@revenge-mod/utils/objects' -import { proxify } from '@revenge-mod/utils/proxy' +import { _logger } from './_internal' import type { Metro } from '@revenge-mod/modules/types' import type { Asset } from './types' -const logger = createLogger('revenge.assets.caches') - const Version = 1 -const CurrentKey = `${Version}.${ClientInfoModule.Build}` +const Key = `${Version}.${ClientInfoModule.Build}` -const CacheStorage = getStorage('revenge/assets', { - default: { k: CurrentKey, c: {} }, +// In-memory cache +export const cache: Cache = {} + +const CacheStorage = getStorage(`revenge/assets.${Key}`, { + default: cache, directory: 'cache', }) -export const cache: Cache['c'] = proxify( - () => { - if (CacheStorage.cache) return CacheStorage.cache.c - }, - { hint: 'object' }, -)! +_logger.log(`[${performance.now()}] CacheStorage instance created`) // biome-ignore lint/complexity/useArrowFunction: Arrow functions are not supported -CacheStorage.get().then(async function (cache) { +CacheStorage.get().then(async function (cache_) { // TODO(lib/assets/caches): This loads way too late and requires native interop to load earlier - logger.log(`Loaded cache version: ${cache.k}`) - if (cache.k !== CurrentKey) { - await CacheStorage.delete() - await CacheStorage.set({ k: CurrentKey, c: {} }) + + // If we are not using the default value, merge it into the in-memory cache, then point the storage cache to the in-memory cache. + if (cache_ !== cache) { + _logger.log(`[${performance.now()}] CacheStorage loaded`) + Object.assign(cache, cache_) + CacheStorage.cache = cache } }) export interface Cache { - k: string - c: { - [key: Asset['name']]: { - [key: Asset['type']]: Metro.ModuleID - } + [key: Asset['name']]: { + [key: Asset['type']]: Metro.ModuleID } } -// const save = debounce(() => CacheStorage.set({}), 1000) +const save = debounce(() => CacheStorage.set({}), 1000) export async function cacheAsset(asset: Asset, moduleId: Metro.ModuleID) { // Merge directly into the cache, only debouncing actual writes - mergeDeep(CacheStorage.cache!.c, { + mergeDeep(cache, { [asset.name]: { [asset.type]: moduleId, }, }) - // Commented out because caching currently does not work properly because it is loaded too late - // await save() + await save() } diff --git a/lib/assets/src/preinit.ts b/lib/assets/src/preinit.ts index 79c28b9..cd6fa59 100644 --- a/lib/assets/src/preinit.ts +++ b/lib/assets/src/preinit.ts @@ -2,8 +2,8 @@ import { _initing, _uninits } from '@revenge-mod/modules/_/metro' import { byName, byProps } from '@revenge-mod/modules/finders/filters' import { waitForModules } from '@revenge-mod/modules/finders/wait' import { getModuleDependencies } from '@revenge-mod/modules/metro/utils' -import { _metas, _overrides } from './_internal' -import { cacheAsset } from './caches' +import { _logger, _metas, _overrides } from './_internal' +import { cache, cacheAsset } from './caches' import type { ReactNative } from '@revenge-mod/react/types' import type { Asset, ReactNativeAsset } from './types' @@ -25,15 +25,27 @@ const unsubAR = waitForModules( if (getModuleDependencies(id)!.length) { unsubAR() - // TODO(assets/patches): conditionally run this if cache does not exist - // More fragile way, but also more performant: - // There is exactly one asset before the reexported asset registry :/ - const firstAssetModuleId = id - 1 - for (const mId of _uninits) { - if (mId < firstAssetModuleId) continue + _logger.log(`[${performance.now()}] Registry found: ${id}`) - const deps = getModuleDependencies(mId)! - if (deps.length === 1 && deps[0] === id) __r(mId) + // Make sure we don't try to recache assets if it is already cached. + let uncached = true + for (const _ in cache) { + uncached = false + break + } + + if (uncached) { + _logger.log('Caching assets...') + + // More fragile way, but also more performant: + // There is exactly one asset before the reexported asset registry :/ + const firstAssetModuleId = id - 1 + for (const mId of _uninits) { + if (mId < firstAssetModuleId) continue + + const deps = getModuleDependencies(mId)! + if (deps.length === 1 && deps[0] === id) __r(mId) + } } // We already patched the original asset registry, so we don't need to patch again.