From f32a0d6090a34e02794eac8578ec8cc55d7ffeee Mon Sep 17 00:00:00 2001 From: Trezy Date: Wed, 17 Jul 2024 19:34:45 -0500 Subject: [PATCH] feat: add the `useAssets` hook --- src/helpers/getAssetKey.ts | 18 +++++++ src/hooks/useAsset.ts | 9 ++-- src/hooks/useAssets.ts | 84 ++++++++++++++++++++++++++++++++ src/index.ts | 1 + src/typedefs/UnresolvedAsset.ts | 3 ++ src/typedefs/UseAssetsOptions.ts | 18 +++++++ 6 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 src/helpers/getAssetKey.ts create mode 100644 src/hooks/useAssets.ts create mode 100644 src/typedefs/UnresolvedAsset.ts create mode 100644 src/typedefs/UseAssetsOptions.ts diff --git a/src/helpers/getAssetKey.ts b/src/helpers/getAssetKey.ts new file mode 100644 index 00000000..ef58e123 --- /dev/null +++ b/src/helpers/getAssetKey.ts @@ -0,0 +1,18 @@ +import type { UnresolvedAsset } from '../typedefs/UnresolvedAsset'; + +/** Retrieves the key from an unresolved asset. */ +export function getAssetKey(asset: UnresolvedAsset) +{ + let assetKey; + + if (typeof asset === 'string') + { + assetKey = asset; + } + else + { + assetKey = (asset.alias ?? asset.src) as string; + } + + return assetKey; +} diff --git a/src/hooks/useAsset.ts b/src/hooks/useAsset.ts index 737b87d3..0c23ab01 100644 --- a/src/hooks/useAsset.ts +++ b/src/hooks/useAsset.ts @@ -2,7 +2,7 @@ import { Assets, Cache, } from 'pixi.js'; -import { getAssetKeyFromOptions } from '../helpers/getAssetKeyFromOptions.ts'; +import { getAssetKey } from '../helpers/getAssetKey.ts'; import type { ProgressCallback, @@ -14,7 +14,10 @@ import type { ErrorCallback } from '../typedefs/ErrorCallback.ts'; const errorCache: Map = new Map(); -/** Loads assets, returning a hash of assets once they're loaded. */ +/** + * Loads assets, returning a hash of assets once they're loaded. + * @deprecated Use `useAssets` instead. + */ export function useAsset( /** @description Asset options. */ options: (UnresolvedAsset & AssetRetryOptions) | string, @@ -43,7 +46,7 @@ export function useAsset( retryOnFailure = true, } = typeof options !== 'string' ? options : {}; - const assetKey = getAssetKeyFromOptions(options); + const assetKey = getAssetKey(options); if (!Cache.has(assetKey)) { diff --git a/src/hooks/useAssets.ts b/src/hooks/useAssets.ts new file mode 100644 index 00000000..1c8c37f7 --- /dev/null +++ b/src/hooks/useAssets.ts @@ -0,0 +1,84 @@ +import { + Assets, + Cache, +} from 'pixi.js'; +import { getAssetKey } from '../helpers/getAssetKey.ts'; + +import type { AssetRetryState } from '../typedefs/AssetRetryState.ts'; +import type { UnresolvedAsset } from '../typedefs/UnresolvedAsset.ts'; +import type { UseAssetsOptions } from '../typedefs/UseAssetsOptions.ts'; + +const errorCache: Map = new Map(); + +/** Loads assets, returning a hash of assets once they're loaded. */ +export function useAssets( + /** @description Assets to be loaded. */ + assets: UnresolvedAsset[], + /** @description Asset options. */ + options: UseAssetsOptions = {}, +): T[] +{ + if (typeof window === 'undefined') + { + throw Object.assign(Error('`useAsset` will only run on the client.'), { + digest: 'BAILOUT_TO_CLIENT_SIDE_RENDERING', + }); + } + + const { + maxRetries = 3, + onError, + onProgress, + retryOnFailure = true, + } = options; + + const allAssetsAreLoaded = assets.some((asset: UnresolvedAsset) => Cache.has(getAssetKey(asset))); + + if (!allAssetsAreLoaded) + { + let state = errorCache.get(assets); + + // Rethrow the cached error if we are not retrying on failure or have reached the max retries + if (state && (!retryOnFailure || state.retries > maxRetries)) + { + if (typeof onError === 'function') + { + onError(state.error); + } + else + { + throw state.error; + } + } + + throw Assets + .load(assets, (progressValue) => + { + if (typeof onProgress === 'function') + { + onProgress(progressValue); + } + }) + .catch((error) => + { + if (!state) + { + state = { + error, + retries: 0, + }; + } + + errorCache.set(assets, { + ...state, + error, + retries: state.retries + 1, + }); + }); + } + + const assetKeys = assets.map((asset: UnresolvedAsset) => getAssetKey(asset)); + const resolvedAssetsDictionary = Assets.get(assetKeys) as Record; + + return assets.map((_asset: UnresolvedAsset, index: number) => resolvedAssetsDictionary[index]); +} diff --git a/src/index.ts b/src/index.ts index 5f1f4bc9..1a264a3e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,5 +4,6 @@ export * from './global.ts'; export { extend } from './helpers/extend.ts'; export { useApp } from './hooks/useApp.ts'; export { useAsset } from './hooks/useAsset.ts'; +export { useAssets } from './hooks/useAssets.ts'; export { useExtend } from './hooks/useExtend.ts'; export { useTick } from './hooks/useTick.ts'; diff --git a/src/typedefs/UnresolvedAsset.ts b/src/typedefs/UnresolvedAsset.ts new file mode 100644 index 00000000..fdfa5cb1 --- /dev/null +++ b/src/typedefs/UnresolvedAsset.ts @@ -0,0 +1,3 @@ +import type { UnresolvedAsset as PixiUnresolvedAsset } from 'pixi.js'; + +export type UnresolvedAsset = PixiUnresolvedAsset | string; diff --git a/src/typedefs/UseAssetsOptions.ts b/src/typedefs/UseAssetsOptions.ts new file mode 100644 index 00000000..02c82c6c --- /dev/null +++ b/src/typedefs/UseAssetsOptions.ts @@ -0,0 +1,18 @@ +import { type ErrorCallback } from './ErrorCallback.ts'; + +import type { ProgressCallback } from 'pixi.js'; + +export interface UseAssetsOptions +{ + /** @description The maximum number of retries allowed before we give up on loading this asset. */ + maxRetries?: number + + /** @description A function to be called when if the asset loader encounters an error. */ + onError?: ErrorCallback, + + /** @description A function to be called when the asset loader reports loading progress. */ + onProgress?: ProgressCallback, + + /** @description Whether to try loading this asset again if it fails. */ + retryOnFailure?: boolean +}