feat(lib/assets)!: set metadata on asset objects, remove redundant APIs, add new APIs and parameters
This commit is contained in:
parent
3903fe0a4b
commit
e510eea36e
7 changed files with 119 additions and 108 deletions
|
|
@ -1,12 +1,4 @@
|
|||
import { createLogger } from '@revenge-mod/utils/logger'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { Asset, AssetId } from './types'
|
||||
import type { Asset, CustomAsset } from './types'
|
||||
|
||||
export const _logger = createLogger('revenge.assets')
|
||||
|
||||
export const _customs = new Set<Asset>()
|
||||
export const _customs = new Set<CustomAsset>()
|
||||
export const _overrides = new WeakMap<Asset, Asset>()
|
||||
export const _metas = new WeakMap<
|
||||
Asset,
|
||||
[assetId: AssetId, moduleId: Metro.ModuleID]
|
||||
>()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { ClientInfoModule } from '@revenge-mod/discord/native'
|
|||
import { getStorage } from '@revenge-mod/storage'
|
||||
import { debounce } from '@revenge-mod/utils/callbacks'
|
||||
import { mergeDeep } from '@revenge-mod/utils/objects'
|
||||
import { _logger } from './_internal'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { Asset } from './types'
|
||||
|
||||
|
|
@ -18,15 +17,12 @@ const CacheStorage = getStorage<Cache>(`revenge/assets.${Key}`, {
|
|||
directory: 'cache',
|
||||
})
|
||||
|
||||
_logger.log(`[${performance.now()}] CacheStorage instance created`)
|
||||
|
||||
// biome-ignore lint/complexity/useArrowFunction: Arrow functions are not supported
|
||||
CacheStorage.get().then(async function (cache_) {
|
||||
// TODO(lib/assets/caches): This loads way too late and requires native interop to load earlier
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
import { ReactNative } from '@revenge-mod/react'
|
||||
import { _customs, _metas, _overrides } from './_internal'
|
||||
import { _customs, _overrides } from './_internal'
|
||||
import { cache } from './caches'
|
||||
import { AssetRegistry } from './preinit'
|
||||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { Asset, AssetId, RegisterableAsset } from './types'
|
||||
import { AssetsRegistry } from './preinit'
|
||||
import type {
|
||||
Asset,
|
||||
AssetId,
|
||||
CustomAsset,
|
||||
PackagerAsset,
|
||||
RegisterableAsset,
|
||||
} from './types'
|
||||
|
||||
export { AssetRegistry, AssetRegistryModuleId } from './preinit'
|
||||
export {
|
||||
AssetsRegistry as AssetRegistry,
|
||||
AssetsRegistryModuleId as AssetRegistryModuleId,
|
||||
} from './preinit'
|
||||
|
||||
// iOS cannot display SVGs
|
||||
let _preferredType: Asset['type'] =
|
||||
|
|
@ -21,25 +29,44 @@ export function setPreferredAssetType(type: Asset['type']) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Yields all registered assets, including ones with same name but different types, custom assets, and overriden assets.
|
||||
* Yields all assets, both packager and custom.
|
||||
*/
|
||||
export function* getAssets(): Generator<Asset> {
|
||||
for (const name of Object.keys(cache!))
|
||||
for (const mid of Object.values(cache[name]))
|
||||
yield AssetRegistry.getAssetByID(__r(mid)) as Asset
|
||||
yield* getPackagerAssets()
|
||||
yield* getCustomAssets()
|
||||
}
|
||||
|
||||
/**
|
||||
* Yields all registered custom assets.
|
||||
*/
|
||||
export function* getCustomAssets(): Generator<CustomAsset> {
|
||||
for (const asset of _customs) yield asset
|
||||
}
|
||||
|
||||
/**
|
||||
* Yields all registered packager assets, including ones with same name but different types.
|
||||
*/
|
||||
export function* getPackagerAssets(): Generator<PackagerAsset> {
|
||||
for (const name in cache) {
|
||||
const reg = cache[name]
|
||||
for (const type in reg)
|
||||
yield AssetsRegistry.getAssetByID(__r(reg[type]))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an asset by its name.
|
||||
* If more than one asset is registered with the same name, this will return the one with the preferred type, or the first registered one.
|
||||
*
|
||||
* @param name The asset name.
|
||||
* @param type The preferred asset type, defaults to the current preferred type.
|
||||
*/
|
||||
export function getAssetByName(name: string): Asset | undefined {
|
||||
const id = getAssetIdByName(name)
|
||||
if (id !== undefined) return AssetRegistry.getAssetByID(id)
|
||||
export function getAssetByName(
|
||||
name: string,
|
||||
type = _preferredType,
|
||||
): Asset | undefined {
|
||||
const id = getAssetIdByName(name, type)
|
||||
if (id !== undefined) return AssetsRegistry.getAssetByID(id)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -56,47 +83,31 @@ export function getAssetsByName(
|
|||
|
||||
return Object.entries(reg).reduce(
|
||||
(acc, [type, mid]) => {
|
||||
acc[type as Asset['type']] = AssetRegistry.getAssetByID(__r(mid))!
|
||||
acc[type as Asset['type']] = AssetsRegistry.getAssetByID(__r(mid))!
|
||||
return acc
|
||||
},
|
||||
{} as Record<Asset['type'], Asset>,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of an asset.
|
||||
*
|
||||
* @param asset The asset to get the ID for.
|
||||
*/
|
||||
export function getAssetId(asset: Asset): AssetId | undefined {
|
||||
return _metas.get(asset)?.[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an asset ID by its name.
|
||||
* If more than one asset is registered with the same name, this will return the one with the preferred type, or the first registered one.
|
||||
*
|
||||
* @param name The asset name.
|
||||
* @param type The preferred asset type, defaults to the current preferred type.
|
||||
*/
|
||||
export function getAssetIdByName(name: string): AssetId | undefined {
|
||||
export function getAssetIdByName(
|
||||
name: string,
|
||||
type = _preferredType,
|
||||
): AssetId | undefined {
|
||||
const reg = cache[name]
|
||||
if (!reg) return
|
||||
|
||||
const mid = reg[_preferredType] ?? reg[Object.keys(reg)[0]!]
|
||||
const mid = reg[type] ?? reg[Object.keys(reg)[0]!]
|
||||
if (mid !== undefined) return __r(mid)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the module ID that registered the asset. Returns `-1` if the asset is a custom asset.
|
||||
*
|
||||
* @param asset The asset to get the module ID for.
|
||||
*/
|
||||
export function getAssetModuleId(
|
||||
asset: Asset,
|
||||
): Metro.ModuleID | -1 | undefined {
|
||||
return _metas.get(asset)?.[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an asset with the given name.
|
||||
*
|
||||
|
|
@ -109,10 +120,10 @@ export function registerAsset(asset: RegisterableAsset): AssetId {
|
|||
`Asset with name ${asset.name} and type ${asset.type} already exists!`,
|
||||
)
|
||||
|
||||
_customs.add(asset)
|
||||
_customs.add(asset as CustomAsset)
|
||||
|
||||
// @ts-expect-error
|
||||
return AssetRegistry.registerAsset(asset)
|
||||
return AssetsRegistry.registerAsset(asset)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,20 +2,20 @@ 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 { _logger, _metas, _overrides } from './_internal'
|
||||
import { _overrides } from './_internal'
|
||||
import { cache, cacheAsset } from './caches'
|
||||
import type { ReactNative } from '@revenge-mod/react/types'
|
||||
import type { Asset, ReactNativeAsset } from './types'
|
||||
import type { Asset, PackagerAsset } from './types'
|
||||
|
||||
export let AssetRegistry: ReactNative.AssetsRegistry
|
||||
export let AssetRegistryModuleId: number
|
||||
export let AssetsRegistry: ReactNative.AssetsRegistry
|
||||
export let AssetsRegistryModuleId: number
|
||||
|
||||
// Tracking/caching assets
|
||||
const unsubAR = waitForModules(
|
||||
byProps<ReactNative.AssetsRegistry>('registerAsset'),
|
||||
(exports, id) => {
|
||||
AssetRegistryModuleId = id
|
||||
AssetRegistry = exports as ReactNative.AssetsRegistry
|
||||
AssetsRegistryModuleId = id
|
||||
AssetsRegistry = exports as ReactNative.AssetsRegistry
|
||||
|
||||
// There are two matching exports. One is the original, and one is a re-export.
|
||||
// The original asset-registry is simply required by the re-exported one with no changes.
|
||||
|
|
@ -25,8 +25,6 @@ const unsubAR = waitForModules(
|
|||
if (getModuleDependencies(id)!.length) {
|
||||
unsubAR()
|
||||
|
||||
_logger.log(`[${performance.now()}] Registry found: ${id}`)
|
||||
|
||||
// Make sure we don't try to recache assets if it is already cached.
|
||||
let uncached = true
|
||||
for (const _ in cache) {
|
||||
|
|
@ -35,8 +33,6 @@ const unsubAR = waitForModules(
|
|||
}
|
||||
|
||||
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
|
||||
|
|
@ -54,16 +50,15 @@ const unsubAR = waitForModules(
|
|||
|
||||
const orig = exports.registerAsset
|
||||
exports.registerAsset = (asset: Asset) => {
|
||||
const result = orig(asset as ReactNativeAsset)
|
||||
const result = orig(asset as PackagerAsset)
|
||||
|
||||
// Cache packager assets only
|
||||
if ((asset as ReactNativeAsset).__packager_asset) {
|
||||
cacheAsset(asset, _initing)
|
||||
// In-memory cache
|
||||
_metas.set(asset, [result, _initing])
|
||||
// Cache and set moduleId for packager assets only
|
||||
if ((asset as PackagerAsset).__packager_asset) {
|
||||
asset.moduleId = _initing!
|
||||
cacheAsset(asset, _initing!)
|
||||
}
|
||||
|
||||
return result
|
||||
return (asset.id = result)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -72,7 +67,7 @@ const unsubAR = waitForModules(
|
|||
const unsubRAS = waitForModules(
|
||||
byName<{
|
||||
addCustomSourceTransformer: (
|
||||
transformer: (arg: { asset: Asset }) => ReactNativeAsset,
|
||||
transformer: (arg: { asset: Asset }) => PackagerAsset,
|
||||
) => void
|
||||
}>('resolveAssetSource'),
|
||||
rAS => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,25 @@
|
|||
import type { Metro } from '@revenge-mod/modules/types'
|
||||
import type { ReactNative } from '@revenge-mod/react/types'
|
||||
|
||||
export type Asset = ReactNativeAsset | RevengeAsset
|
||||
export type Asset = PackagerAsset | CustomAsset
|
||||
export type AssetId = number
|
||||
|
||||
export type ReactNativeAsset = ReactNative.AssetsRegistry.PackagerAsset
|
||||
export interface RevengeAsset
|
||||
extends Pick<ReactNativeAsset, 'name' | 'width' | 'height' | 'type'> {
|
||||
export type PackagerAsset = ReactNative.AssetsRegistry.PackagerAsset
|
||||
export interface CustomAsset
|
||||
extends Pick<PackagerAsset, 'name' | 'width' | 'height' | 'type' | 'id'> {
|
||||
uri: string
|
||||
moduleId?: undefined
|
||||
}
|
||||
|
||||
export type RegisterableAsset = Omit<RevengeAsset, 'id'>
|
||||
export type RegisterableAsset = Omit<CustomAsset, 'id'>
|
||||
|
||||
declare module '@revenge-mod/react/types' {
|
||||
export namespace ReactNative {
|
||||
export namespace AssetsRegistry {
|
||||
export interface PackagerAsset {
|
||||
id: AssetId
|
||||
moduleId: Metro.ModuleID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getAssetByName, getAssetModuleId } from '@revenge-mod/assets'
|
||||
import { getAssetByName } from '@revenge-mod/assets'
|
||||
import { TokensModuleId } from '@revenge-mod/discord/common'
|
||||
import { byDependencies, byProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
|
||||
|
|
@ -45,7 +45,7 @@ export function lookupGeneratedIconComponent<N extends string>(
|
|||
const mids = []
|
||||
|
||||
for (const name of assetNames) {
|
||||
const mid = getAssetModuleId(getAssetByName(name)!)
|
||||
const mid = getAssetByName(name)?.moduleId
|
||||
if (!mid) return
|
||||
|
||||
mids.push(mid)
|
||||
|
|
@ -53,7 +53,7 @@ export function lookupGeneratedIconComponent<N extends string>(
|
|||
|
||||
filter = [...MultiIconComponentFilterBase, ...mids, 2]
|
||||
} else {
|
||||
const mid = getAssetModuleId(getAssetByName(compName)!)
|
||||
const mid = getAssetByName(compName)?.moduleId
|
||||
if (!mid) return
|
||||
|
||||
IconComponentFilter[4] = mid
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getAssetId, getAssetModuleId, getAssets } from '@revenge-mod/assets'
|
||||
import { getAssets } from '@revenge-mod/assets'
|
||||
import { AlertActionCreators } from '@revenge-mod/discord/actions'
|
||||
import { Design } from '@revenge-mod/discord/design'
|
||||
import { Clipboard } from '@revenge-mod/externals/react-native-clipboard'
|
||||
|
|
@ -24,24 +24,13 @@ const UndisplayableFallback = {
|
|||
|
||||
export default function AssetBrowserSettingScreen() {
|
||||
const [search, setSearch] = React.useState('')
|
||||
const assets = React.useMemo(
|
||||
() =>
|
||||
[...getAssets()].map(
|
||||
asset =>
|
||||
[
|
||||
getAssetId(asset)!,
|
||||
getAssetModuleId(asset)!,
|
||||
asset,
|
||||
] as const,
|
||||
),
|
||||
[getAssets],
|
||||
)
|
||||
const assets = React.useMemo(() => [...getAssets()], [getAssets])
|
||||
|
||||
const filteredAssets = React.useMemo(
|
||||
() =>
|
||||
!search.length
|
||||
? assets
|
||||
: assets.filter(([, , asset]) =>
|
||||
: assets.filter(asset =>
|
||||
asset.name.toLowerCase().includes(search.toLowerCase()),
|
||||
),
|
||||
[assets, search],
|
||||
|
|
@ -53,13 +42,17 @@ export default function AssetBrowserSettingScreen() {
|
|||
<FlashList.FlashList
|
||||
data={filteredAssets}
|
||||
estimatedItemSize={80}
|
||||
keyExtractor={([id]) => id.toString()}
|
||||
renderItem={({ item: [id, moduleId, asset], index }) => (
|
||||
keyExtractor={asset =>
|
||||
asset.id
|
||||
? asset.id.toString()
|
||||
: `${asset.name}.${asset.type}`
|
||||
}
|
||||
renderItem={({ item: asset, index }) => (
|
||||
<AssetDisplay
|
||||
asset={asset}
|
||||
end={index === assets.length - 1}
|
||||
id={id}
|
||||
moduleId={moduleId}
|
||||
end={index === filteredAssets.length - 1}
|
||||
id={asset.id}
|
||||
moduleId={asset.moduleId}
|
||||
start={!index}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -76,7 +69,7 @@ function AssetDisplay({
|
|||
end,
|
||||
}: {
|
||||
id: AssetId
|
||||
moduleId: Metro.ModuleID
|
||||
moduleId?: Metro.ModuleID
|
||||
asset: Asset
|
||||
start?: boolean
|
||||
end?: boolean
|
||||
|
|
@ -85,7 +78,7 @@ function AssetDisplay({
|
|||
const metadata = [
|
||||
['ID', id.toString()],
|
||||
['Type', asset.type],
|
||||
['Module ID', moduleId.toString()],
|
||||
['Module ID', moduleId?.toString()],
|
||||
] as const
|
||||
|
||||
return (
|
||||
|
|
@ -111,7 +104,7 @@ function AssetDisplay({
|
|||
onPress={() => openAssetDisplayAlert(asset, id, metadata)}
|
||||
start={start}
|
||||
subLabel={metadata
|
||||
.map(([name, value]) => `${name}: ${value}`)
|
||||
.map(([name, value]) => `${name}: ${value ?? 'N/A'}`)
|
||||
.join(' • ')}
|
||||
variant={isDisplayable ? 'default' : 'danger'}
|
||||
/>
|
||||
|
|
@ -121,7 +114,9 @@ function AssetDisplay({
|
|||
function openAssetDisplayAlert(
|
||||
asset: Asset,
|
||||
id: AssetId,
|
||||
metadata: Readonly<Readonly<[string, string]>[]> | [string, string][],
|
||||
metadata:
|
||||
| Readonly<Readonly<[string, string | undefined]>[]>
|
||||
| [string, string | undefined][],
|
||||
) {
|
||||
const isDisplayable = Displayable.has(asset.type)
|
||||
|
||||
|
|
@ -137,14 +132,23 @@ function openAssetDisplayAlert(
|
|||
<PreviewUnavailable type={asset.type} />
|
||||
)}
|
||||
<TableRowGroup>
|
||||
{metadata.map(([name, value]) => (
|
||||
<TableRow
|
||||
key={id}
|
||||
label={name}
|
||||
onPress={() => Clipboard.setString(value)}
|
||||
subLabel={value}
|
||||
/>
|
||||
))}
|
||||
{metadata.map(([name, value]) =>
|
||||
value ? (
|
||||
<TableRow
|
||||
key={id}
|
||||
label={name}
|
||||
onPress={() => Clipboard.setString(value)}
|
||||
subLabel={value}
|
||||
/>
|
||||
) : (
|
||||
<TableRow
|
||||
key={id}
|
||||
label={name}
|
||||
subLabel="N/A"
|
||||
disabled
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</TableRowGroup>
|
||||
<Text color="text-danger" variant="text-xs/semibold">
|
||||
Note: Asset IDs and module IDs are not consistent
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue