-
-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #504 from pixijs/501-feature-request-error-handlin…
…g-for-useasset Add `onError` callback to `useAsset`
- Loading branch information
Showing
13 changed files
with
349 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export const UseAssetsStatus: Record<string, 'error' | 'pending' | 'success'> = { | ||
ERROR: 'error', | ||
PENDING: 'pending', | ||
SUCCESS: 'success', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import type { UnresolvedAsset } from '../typedefs/UnresolvedAsset'; | ||
|
||
/** Retrieves the key from an unresolved asset. */ | ||
export function getAssetKey<T>(asset: UnresolvedAsset<T>) | ||
{ | ||
let assetKey; | ||
|
||
if (typeof asset === 'string') | ||
{ | ||
assetKey = asset; | ||
} | ||
else | ||
{ | ||
assetKey = (asset.alias ?? asset.src) as string; | ||
} | ||
|
||
return assetKey; | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { | ||
Assets, | ||
Cache, | ||
} from 'pixi.js'; | ||
import { useState } from 'react'; | ||
import { UseAssetsStatus } from '../constants/UseAssetsStatus.ts'; | ||
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'; | ||
import type { UseAssetsResult } from '../typedefs/UseAssetsResult.ts'; | ||
|
||
const errorCache: Map<UnresolvedAsset, AssetRetryState> = new Map(); | ||
|
||
function assetsLoadedTest<T>(asset: UnresolvedAsset<T>) | ||
{ | ||
return Cache.has(getAssetKey(asset)); | ||
} | ||
|
||
/** Loads assets, returning a hash of assets once they're loaded. */ | ||
export function useAssets<T = any>( | ||
/** @description Assets to be loaded. */ | ||
assets: UnresolvedAsset<T>[], | ||
|
||
/** @description Asset options. */ | ||
options: UseAssetsOptions = {}, | ||
): UseAssetsResult<T> | ||
{ | ||
const [state, setState] = useState<UseAssetsResult<T>>({ | ||
assets: Array(assets.length).fill(null), | ||
isError: false, | ||
isPending: true, | ||
isSuccess: false, | ||
status: UseAssetsStatus.PENDING, | ||
}); | ||
|
||
if (typeof window === 'undefined') | ||
{ | ||
return state; | ||
} | ||
|
||
const { | ||
maxRetries = 3, | ||
onError, | ||
onProgress, | ||
retryOnFailure = true, | ||
} = options; | ||
|
||
const allAssetsAreLoaded = assets.some(assetsLoadedTest<T>); | ||
|
||
if (!allAssetsAreLoaded) | ||
{ | ||
let cachedState = errorCache.get(assets); | ||
|
||
// Rethrow the cached error if we are not retrying on failure or have reached the max retries | ||
if (cachedState && (!retryOnFailure || cachedState.retries > maxRetries)) | ||
{ | ||
if (typeof onError === 'function') | ||
{ | ||
onError(cachedState.error); | ||
} | ||
|
||
setState((previousState) => ({ | ||
...previousState, | ||
error: cachedState?.error, | ||
isError: true, | ||
isPending: false, | ||
isSuccess: false, | ||
status: UseAssetsStatus.ERROR, | ||
})); | ||
} | ||
|
||
Assets.load<T>(assets, (progressValue) => | ||
{ | ||
if (typeof onProgress === 'function') | ||
{ | ||
onProgress(progressValue); | ||
} | ||
}) | ||
.then(() => | ||
{ | ||
const assetKeys = assets.map((asset: UnresolvedAsset<T>) => getAssetKey(asset)); | ||
const resolvedAssetsDictionary = Assets.get<T>(assetKeys) as Record<string, T>; | ||
|
||
setState((previousState) => ({ | ||
...previousState, | ||
assets: assets.map((_asset: UnresolvedAsset<T>, index: number) => resolvedAssetsDictionary[index]), | ||
isError: false, | ||
isPending: false, | ||
isSuccess: true, | ||
status: UseAssetsStatus.SUCCESS, | ||
})); | ||
}) | ||
.catch((error) => | ||
{ | ||
if (!cachedState) | ||
{ | ||
cachedState = { | ||
error, | ||
retries: 0, | ||
}; | ||
} | ||
|
||
errorCache.set(assets, { | ||
...cachedState, | ||
error, | ||
retries: cachedState.retries + 1, | ||
}); | ||
}); | ||
} | ||
|
||
return state; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
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<UnresolvedAsset, AssetRetryState> = new Map(); | ||
|
||
function assetsLoadedTest<T>(asset: UnresolvedAsset<T>) | ||
{ | ||
return Cache.has(getAssetKey(asset)); | ||
} | ||
|
||
/** Loads assets, returning a hash of assets once they're loaded. Must be inside of a `<Suspense>` component. */ | ||
export function useSuspenseAssets<T = any>( | ||
/** @description Assets to be loaded. */ | ||
assets: UnresolvedAsset<T>[], | ||
/** @description Asset options. */ | ||
options: UseAssetsOptions = {}, | ||
): T[] | ||
{ | ||
if (typeof window === 'undefined') | ||
{ | ||
throw Object.assign(Error('`useAssets` will only run on the client.'), { | ||
digest: 'BAILOUT_TO_CLIENT_SIDE_RENDERING', | ||
}); | ||
} | ||
|
||
const { | ||
maxRetries = 3, | ||
onError, | ||
onProgress, | ||
retryOnFailure = true, | ||
} = options; | ||
|
||
const allAssetsAreLoaded = assets.some(assetsLoadedTest<T>); | ||
|
||
if (!allAssetsAreLoaded) | ||
{ | ||
let cachedState = errorCache.get(assets); | ||
|
||
// Rethrow the cached error if we are not retrying on failure or have reached the max retries | ||
if (cachedState && (!retryOnFailure || cachedState.retries > maxRetries)) | ||
{ | ||
if (typeof onError === 'function') | ||
{ | ||
onError(cachedState.error); | ||
} | ||
else | ||
{ | ||
throw cachedState.error; | ||
} | ||
} | ||
|
||
throw Assets | ||
.load<T>(assets, (progressValue) => | ||
{ | ||
if (typeof onProgress === 'function') | ||
{ | ||
onProgress(progressValue); | ||
} | ||
}) | ||
.catch((error) => | ||
{ | ||
if (!cachedState) | ||
{ | ||
cachedState = { | ||
error, | ||
retries: 0, | ||
}; | ||
} | ||
|
||
errorCache.set(assets, { | ||
...cachedState, | ||
error, | ||
retries: cachedState.retries + 1, | ||
}); | ||
}); | ||
} | ||
|
||
const assetKeys = assets.map((asset: UnresolvedAsset<T>) => getAssetKey(asset)); | ||
const resolvedAssetsDictionary = Assets.get<T>(assetKeys) as Record<string, T>; | ||
|
||
return assets.map((_asset: UnresolvedAsset<T>, index: number) => resolvedAssetsDictionary[index]); | ||
} |
Oops, something went wrong.