feat(lib): proper caching for assets and modules lib
This commit is contained in:
parent
67345aa82e
commit
dba76a9f81
9 changed files with 112 additions and 115 deletions
|
|
@ -1,51 +1,50 @@
|
|||
// 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 {
|
||||
callBridgeMethod,
|
||||
callBridgeMethodSync,
|
||||
} from '@revenge-mod/modules/native'
|
||||
import { debounce } from '@revenge-mod/utils/callback'
|
||||
import { mergeDeep } from '@revenge-mod/utils/object'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { Asset } from './types'
|
||||
|
||||
const Version = 1
|
||||
const Key = `${Version}.${ClientInfoModule.getConstants().Build}`
|
||||
const ExpectedCacheVersion = 2
|
||||
|
||||
export const Uncached: Cache = {
|
||||
data: {},
|
||||
version: ExpectedCacheVersion,
|
||||
}
|
||||
|
||||
// In-memory cache
|
||||
export const cache: Cache = {}
|
||||
export let cache: Cache =
|
||||
callBridgeMethodSync('revenge.caches.assets.read', []) ?? Uncached
|
||||
|
||||
const CacheStorage = getStorage<Cache>(`revenge/assets.${Key}`, {
|
||||
default: cache,
|
||||
directory: 'cache',
|
||||
})
|
||||
|
||||
// TODO(lib/assets/caches): This loads way too late and requires native interop to load earlier
|
||||
export const cached = CacheStorage.get().then(cache_ => {
|
||||
const cached = cache_ !== cache
|
||||
// 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 (cached) {
|
||||
Object.assign(cache, cache_)
|
||||
CacheStorage.cache = cache
|
||||
}
|
||||
|
||||
return cached
|
||||
})
|
||||
if (cache.version !== ExpectedCacheVersion) {
|
||||
// TODO: Alert to user to update build
|
||||
cache = Uncached
|
||||
}
|
||||
|
||||
export interface Cache {
|
||||
[key: Asset['name']]: {
|
||||
[key: Asset['type']]: Metro.ModuleID
|
||||
data: {
|
||||
[key: Asset['name']]: {
|
||||
[key: Asset['type']]: Metro.ModuleID
|
||||
}
|
||||
}
|
||||
version: number
|
||||
}
|
||||
|
||||
const save = debounce(() => {
|
||||
CacheStorage.set({})
|
||||
callBridgeMethod('revenge.caches.assets.write', [cache.data])
|
||||
}, 1000)
|
||||
|
||||
export function cacheAsset(asset: Asset, moduleId: Metro.ModuleID) {
|
||||
// Merge directly into the cache, only debouncing actual writes
|
||||
mergeDeep(cache, {
|
||||
[asset.name]: {
|
||||
[asset.type]: moduleId,
|
||||
},
|
||||
})
|
||||
const reg = (cache.data[asset.name] ??= {})
|
||||
reg[asset.type] = moduleId
|
||||
|
||||
save()
|
||||
}
|
||||
|
||||
declare module '@revenge-mod/modules/native' {
|
||||
interface Methods {
|
||||
'revenge.caches.assets.read': [[], Cache | null]
|
||||
'revenge.caches.assets.write': [[data: Cache['data']], void]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export function* getCustomAssets(): Generator<CustomAsset> {
|
|||
* Yields all registered packager assets, including ones with same name but different types.
|
||||
*/
|
||||
export function* getPackagerAssets(): Generator<PackagerAsset> {
|
||||
for (const reg of Object.values(cache))
|
||||
for (const reg of Object.values(cache.data))
|
||||
for (const moduleId of Object.values(reg))
|
||||
yield AssetsRegistry.getAssetByID(metroRequire(moduleId))
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ export function getAssetByName(
|
|||
export function getAssetsByName(
|
||||
name: string,
|
||||
): Record<Asset['type'], Asset> | undefined {
|
||||
const reg = cache[name]
|
||||
const reg = cache.data[name]
|
||||
if (!reg) return
|
||||
|
||||
return Object.entries(reg).reduce(
|
||||
|
|
@ -106,7 +106,7 @@ export function getAssetIdByName(
|
|||
name: string,
|
||||
type?: Asset['type'],
|
||||
): AssetId | undefined {
|
||||
const reg = cache[name]
|
||||
const reg = cache.data[name]
|
||||
if (!reg) return
|
||||
|
||||
if (type !== undefined) {
|
||||
|
|
@ -127,7 +127,7 @@ export function getAssetIdByName(
|
|||
* @returns The asset ID.
|
||||
*/
|
||||
export function registerAsset(asset: RegisterableAsset): AssetId {
|
||||
if (cache[asset.name]?.[asset.type] !== undefined)
|
||||
if (cache.data[asset.name]?.[asset.type] !== undefined)
|
||||
throw new Error(
|
||||
`Asset with name ${asset.name} and type ${asset.type} already exists!`,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
import { getModuleDependencies } from '@revenge-mod/modules/metro/utils'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
import { aOverrides } from './_internal'
|
||||
import { cacheAsset, cached } from './caches'
|
||||
import { cache, cacheAsset, Uncached } from './caches'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { ReactNative } from '@revenge-mod/react/types'
|
||||
import type { Asset, PackagerAsset } from './types'
|
||||
|
|
@ -52,24 +52,20 @@ const unsubAR = waitForModules(
|
|||
if (getModuleDependencies(id)!.length) {
|
||||
unsubAR()
|
||||
|
||||
// TODO(lib/assets/caches): Ideally we should not wait for the cache promise here, see the promise impl for more info.
|
||||
cached.then(cached => {
|
||||
if (!cached) {
|
||||
// Resolve reference once and keep in closure
|
||||
const metroRequire = __r
|
||||
if (cache === Uncached) {
|
||||
// Resolve reference once and keep in closure
|
||||
const metroRequire = __r
|
||||
|
||||
// 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 mUninitialized) {
|
||||
if (mId < firstAssetModuleId) continue
|
||||
// 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 mUninitialized) {
|
||||
if (mId < firstAssetModuleId) continue
|
||||
|
||||
const deps = getModuleDependencies(mId)!
|
||||
if (deps.length === 1 && deps[0] === id)
|
||||
metroRequire(mId)
|
||||
}
|
||||
const deps = getModuleDependencies(mId)!
|
||||
if (deps.length === 1 && deps[0] === id) metroRequire(mId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// We already patched the original asset registry, so we don't need to patch again.
|
||||
return
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from '@revenge-mod/modules/finders/filters'
|
||||
import { getModuleDependencies } from '@revenge-mod/modules/metro/utils'
|
||||
import { asap, noop } from '@revenge-mod/utils/callback'
|
||||
import { cached, cacheFilterResultForId } from '#modules/src/caches'
|
||||
import { cache, cacheFilterResultForId, Uncached } from '#modules/src/caches'
|
||||
import { FilterResultFlags } from '#modules/src/finders/_internal'
|
||||
import { FilterFlag } from '#modules/src/finders/filters/constants'
|
||||
import type {
|
||||
|
|
@ -140,13 +140,11 @@ waitForModules(withStore(), (store, id) => {
|
|||
Stores[name] = store
|
||||
})
|
||||
|
||||
cached.then(cached => {
|
||||
if (!cached)
|
||||
asap(() => {
|
||||
const lookup = lookupModules(withStore(), {
|
||||
uninitialized: true,
|
||||
})
|
||||
|
||||
while (lookup.next().done);
|
||||
if (cache === Uncached)
|
||||
asap(() => {
|
||||
const lookup = lookupModules(withStore(), {
|
||||
uninitialized: true,
|
||||
})
|
||||
})
|
||||
|
||||
while (lookup.next().done);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
"./native": {
|
||||
"default": "./src/native/index.ts"
|
||||
},
|
||||
},
|
||||
"./types": {
|
||||
"types": "./src/types.ts"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,61 @@
|
|||
// TODO(lib/modules/caches): 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 {
|
||||
callBridgeMethod,
|
||||
callBridgeMethodSync,
|
||||
} from '@revenge-mod/modules/native'
|
||||
import { debounce } from '@revenge-mod/utils/callback'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { FilterResultFlag } from './finders/_internal'
|
||||
import type { Filter } from './finders/filters'
|
||||
|
||||
const Version = 2
|
||||
const Key = `${Version}.${ClientInfoModule.getConstants().Build}`
|
||||
const ExpectedCacheVersion = 3
|
||||
|
||||
export const Uncached: Cache = {
|
||||
blacklist: [],
|
||||
finds: {},
|
||||
version: ExpectedCacheVersion,
|
||||
}
|
||||
|
||||
// In-memory cache
|
||||
const cache: Cache = { b: [], f: {} }
|
||||
export let cache: Cache =
|
||||
callBridgeMethodSync('revenge.caches.modules.read', []) ?? Uncached
|
||||
|
||||
const CacheStorage = getStorage<Cache>(`revenge/modules.${Key}`, {
|
||||
default: cache,
|
||||
directory: 'cache',
|
||||
})
|
||||
if (cache.version !== ExpectedCacheVersion) {
|
||||
// TODO: Alert to user to update build
|
||||
cache = Uncached
|
||||
}
|
||||
|
||||
// TODO(lib/modules/caches): This loads way too late and requires native interop to load earlier
|
||||
export const cached = CacheStorage.get().then(cache_ => {
|
||||
const cached = cache_ !== cache
|
||||
// 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 (cached) {
|
||||
Object.assign(cache, cache_)
|
||||
CacheStorage.cache = cache
|
||||
}
|
||||
|
||||
return cached
|
||||
})
|
||||
export type Blacklist = Metro.ModuleID[]
|
||||
export type Finds = Record<
|
||||
Filter['key'],
|
||||
Record<Metro.ModuleID, FilterResultFlag> | null
|
||||
>
|
||||
|
||||
export interface Cache {
|
||||
b: Metro.ModuleID[]
|
||||
f: Record<
|
||||
Filter['key'],
|
||||
Record<Metro.ModuleID, FilterResultFlag> | null | undefined
|
||||
>
|
||||
blacklist: Blacklist
|
||||
finds: Finds
|
||||
version: number
|
||||
}
|
||||
|
||||
const save = debounce(() => {
|
||||
CacheStorage.set({})
|
||||
callBridgeMethod('revenge.caches.modules.write', [
|
||||
cache.blacklist,
|
||||
cache.finds,
|
||||
])
|
||||
}, 1000)
|
||||
|
||||
export function cacheBlacklistedModule(id: Metro.ModuleID): boolean {
|
||||
cache.b.push(id)
|
||||
cache.blacklist.push(id)
|
||||
save()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export function cacheFilterResultForId(
|
||||
key: Filter['key'],
|
||||
key: keyof Finds,
|
||||
id: Metro.ModuleID,
|
||||
flag: FilterResultFlag,
|
||||
): FilterResultFlag {
|
||||
const reg = (cache.f[key] ??= {})
|
||||
const reg = (cache.finds[key] ??= {})
|
||||
reg[id] = flag
|
||||
|
||||
save()
|
||||
|
|
@ -61,12 +63,22 @@ export function cacheFilterResultForId(
|
|||
return flag
|
||||
}
|
||||
|
||||
export function cacheFilterNotFound(key: Filter['key']) {
|
||||
cache.f[key] = null
|
||||
export function cacheFilterNotFound(key: keyof Finds) {
|
||||
cache.finds[key] = null
|
||||
}
|
||||
|
||||
export const getCachedFilterRegistry = (
|
||||
key: Filter['key'],
|
||||
): Cache['f'][Filter['key']] | undefined => cache.f[key]
|
||||
export const getFilterMatches = (
|
||||
key: keyof Finds,
|
||||
): Finds[keyof Finds] | undefined => cache.finds[key]
|
||||
|
||||
export const getCachedBlacklistedModules = () => cache.b
|
||||
export const getBlacklistedModules = () => cache.blacklist
|
||||
|
||||
declare module '@revenge-mod/modules/native' {
|
||||
interface Methods {
|
||||
'revenge.caches.modules.read': [[], Cache | null]
|
||||
'revenge.caches.modules.write': [
|
||||
[blacklist: Blacklist, finds: Finds],
|
||||
void,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { getCurrentStack } from '@revenge-mod/utils/error'
|
||||
import { proxify } from '@revenge-mod/utils/proxy'
|
||||
import { cacheFilterNotFound, getCachedFilterRegistry } from '../caches'
|
||||
import { cacheFilterNotFound, getFilterMatches } from '../caches'
|
||||
import { metroRequire } from '../metro/custom'
|
||||
import {
|
||||
mImportedPaths,
|
||||
|
|
@ -163,7 +163,7 @@ export function* lookupModules(filter: Filter, options?: LookupModulesOptions) {
|
|||
if (options?.cached ?? true) {
|
||||
const notInit = !(options?.initialize ?? true)
|
||||
|
||||
const reg = getCachedFilterRegistry(filter.key)
|
||||
const reg = getFilterMatches(filter.key)
|
||||
// Return early if previous lookup was a full lookup and no modules were found
|
||||
if (reg === null) return
|
||||
|
||||
|
|
@ -307,7 +307,7 @@ export function lookupModule(filter: Filter, options?: LookupModulesOptions) {
|
|||
if (options?.cached ?? true) {
|
||||
const notInit = !(options?.initialize ?? true)
|
||||
|
||||
const reg = getCachedFilterRegistry(filter.key)
|
||||
const reg = getFilterMatches(filter.key)
|
||||
// Return early if previous lookup was a full lookup and no modules were found
|
||||
if (reg === null) return NotFoundResult
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { noop } from '@revenge-mod/utils/callback'
|
||||
import { getCachedFilterRegistry } from '../caches'
|
||||
import { getFilterMatches } from '../caches'
|
||||
import { mInitialized } from '../metro/patches'
|
||||
import {
|
||||
onAnyModuleInitialized,
|
||||
|
|
@ -97,13 +97,13 @@ export function waitForModules(
|
|||
options?: WaitForModulesOptions,
|
||||
): WaitForModulesUnsubscribeFunction {
|
||||
if (options?.cached) {
|
||||
const reg = getCachedFilterRegistry(filter.key)
|
||||
const reg = getFilterMatches(filter.key)
|
||||
if (reg === null) return noop
|
||||
|
||||
if (reg) {
|
||||
if (__BUILD_FLAG_DEBUG_MODULE_WAITS__)
|
||||
nativeLoggingHook(
|
||||
`\u001b[32mUsing cached results for wait \u001b[33m${filter.key}\u001b[0m`,
|
||||
`\u001b[32mUsing cached results for wait: \u001b[33m${filter.key}\u001b[0m`,
|
||||
1,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
cacheBlacklistedModule,
|
||||
cached,
|
||||
getCachedBlacklistedModules,
|
||||
} from '../caches'
|
||||
import { cache, cacheBlacklistedModule, Uncached } from '../caches'
|
||||
import {
|
||||
global,
|
||||
metroImportAll,
|
||||
|
|
@ -144,11 +140,8 @@ function handleFactoryCall(
|
|||
/// MODULE PATCHES AND BLACKLISTS
|
||||
|
||||
// Restore blacklists
|
||||
cached.then(cached => {
|
||||
if (cached)
|
||||
for (const id of getCachedBlacklistedModules())
|
||||
mUninitialized.delete(id)
|
||||
})
|
||||
if (cache !== Uncached)
|
||||
for (const id of cache.blacklist) mUninitialized.delete(id)
|
||||
|
||||
const ImportTrackerModuleId = 2
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue