@antha/asset 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -5
- package/dist/antha-asset.mod.d.ts +11 -22
- package/dist/antha-asset.mod.js +35 -42
- package/dist/asset-loader.d.ts +84 -40
- package/dist/asset-loader.js +175 -41
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -14,7 +14,12 @@ npm i @antha/asset
|
|
|
14
14
|
|
|
15
15
|
```TypeScript
|
|
16
16
|
import {AnthaEngine, defineAnthaMod} from '@antha/engine';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
type AnthaAssetModState,
|
|
19
|
+
type AssetLoader,
|
|
20
|
+
createAnthaAssetMod,
|
|
21
|
+
defineAsset,
|
|
22
|
+
} from '@antha/asset';
|
|
18
23
|
|
|
19
24
|
type GameState = AnthaAssetModState & {
|
|
20
25
|
hasLoadedTitle: boolean;
|
|
@@ -31,6 +36,25 @@ const titleAsset = defineAsset({
|
|
|
31
36
|
};
|
|
32
37
|
},
|
|
33
38
|
});
|
|
39
|
+
|
|
40
|
+
async function loadTitleAsset({
|
|
41
|
+
assetLoader,
|
|
42
|
+
}: Readonly<{
|
|
43
|
+
assetLoader: AssetLoader;
|
|
44
|
+
}>) {
|
|
45
|
+
const loadSession = assetLoader.createLoadSession();
|
|
46
|
+
|
|
47
|
+
await assetLoader.bulkLoadAssets(
|
|
48
|
+
[
|
|
49
|
+
titleAsset,
|
|
50
|
+
],
|
|
51
|
+
{
|
|
52
|
+
loadSession,
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
loadSession.complete();
|
|
56
|
+
}
|
|
57
|
+
|
|
34
58
|
const engine = new AnthaEngine<GameState>({
|
|
35
59
|
initState: {
|
|
36
60
|
hasLoadedTitle: false,
|
|
@@ -39,12 +63,12 @@ const engine = new AnthaEngine<GameState>({
|
|
|
39
63
|
createAnthaAssetMod(),
|
|
40
64
|
defineAnthaMod<GameState>({
|
|
41
65
|
modName: 'game-logic',
|
|
42
|
-
|
|
66
|
+
execute({state}) {
|
|
43
67
|
if (state.assetLoader && !state.hasLoadedTitle) {
|
|
44
68
|
state.hasLoadedTitle = true;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
69
|
+
void loadTitleAsset({
|
|
70
|
+
assetLoader: state.assetLoader,
|
|
71
|
+
});
|
|
48
72
|
}
|
|
49
73
|
},
|
|
50
74
|
}),
|
|
@@ -1,21 +1,5 @@
|
|
|
1
1
|
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
2
|
import { AssetLoader } from './asset-loader.js';
|
|
3
|
-
/**
|
|
4
|
-
* Engine state for the Antha asset mod loading screen.
|
|
5
|
-
*
|
|
6
|
-
* @category Internal
|
|
7
|
-
*/
|
|
8
|
-
export type AnthaAssetModLoadingScreenState = {
|
|
9
|
-
/** The total number to load. Once `current` reaches this, loading is considered complete. */
|
|
10
|
-
total: number;
|
|
11
|
-
/**
|
|
12
|
-
* The current number of loaded assets. Once this reaches `total`, the loading is considered
|
|
13
|
-
* complete.
|
|
14
|
-
*/
|
|
15
|
-
current: number;
|
|
16
|
-
currentResourceName?: string | undefined;
|
|
17
|
-
completedAt: DOMHighResTimeStamp | undefined;
|
|
18
|
-
};
|
|
19
3
|
/**
|
|
20
4
|
* State for {@link AnthaAssetMod}.
|
|
21
5
|
*
|
|
@@ -23,8 +7,6 @@ export type AnthaAssetModLoadingScreenState = {
|
|
|
23
7
|
*/
|
|
24
8
|
export type AnthaAssetModState = {
|
|
25
9
|
assetLoader: AssetLoader;
|
|
26
|
-
isShowingLoadingScreen: boolean;
|
|
27
|
-
loadingScreenState: AnthaAssetModLoadingScreenState | undefined;
|
|
28
10
|
};
|
|
29
11
|
/**
|
|
30
12
|
* Configuration options for {@link createAnthaAssetMod}.
|
|
@@ -33,19 +15,25 @@ export type AnthaAssetModState = {
|
|
|
33
15
|
*/
|
|
34
16
|
export type AnthaAssetModOptions = PartialWithUndefined<{
|
|
35
17
|
/**
|
|
36
|
-
* If set to `true`, the default loading screen is not rendered.
|
|
37
|
-
*
|
|
18
|
+
* If set to `true`, the default loading screen is not rendered. Loading-session state remains
|
|
19
|
+
* available for a custom loading screen.
|
|
38
20
|
*
|
|
39
21
|
* @default false
|
|
40
22
|
*/
|
|
41
23
|
hideLoadingScreen: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Duration in milliseconds for the default loading screen's fade-out animation.
|
|
26
|
+
*
|
|
27
|
+
* @default defaultLoadingScreenFadeMs
|
|
28
|
+
*/
|
|
29
|
+
loadingScreenFadeMs: number;
|
|
42
30
|
}>;
|
|
43
31
|
/**
|
|
44
32
|
* Duration in milliseconds for the loading screen fade-out animation.
|
|
45
33
|
*
|
|
46
34
|
* @category Internal
|
|
47
35
|
*/
|
|
48
|
-
export declare const
|
|
36
|
+
export declare const defaultLoadingScreenFadeMs = 1000;
|
|
49
37
|
/**
|
|
50
38
|
* Duration in milliseconds for the progress bar grow transition.
|
|
51
39
|
*
|
|
@@ -62,7 +50,8 @@ export declare const AnthaAssetLoadingScreen: import("element-vir").DeclarativeE
|
|
|
62
50
|
dotCount: number;
|
|
63
51
|
completed: boolean;
|
|
64
52
|
currentResourceName: string | undefined;
|
|
65
|
-
|
|
53
|
+
loadingScreenFadeMs: number;
|
|
54
|
+
}, {}, {}, "antha-asset-loading-screen-completed", "antha-asset-loading-screen-fade-ms", readonly [], readonly []>;
|
|
66
55
|
/**
|
|
67
56
|
* The Antha Asset mod, created by {@link createAnthaAssetMod}.
|
|
68
57
|
*
|
package/dist/antha-asset.mod.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { defineAnthaMod } from '@antha/engine';
|
|
2
|
+
import { addSuffix } from '@augment-vir/common';
|
|
2
3
|
import { css, defineElement, html } from 'element-vir';
|
|
3
|
-
import {
|
|
4
|
+
import { setCssVarValue } from 'lit-css-vars';
|
|
5
|
+
import { AssetLoader } from './asset-loader.js';
|
|
4
6
|
/**
|
|
5
7
|
* Duration in milliseconds for the loading screen fade-out animation.
|
|
6
8
|
*
|
|
7
9
|
* @category Internal
|
|
8
10
|
*/
|
|
9
|
-
export const
|
|
11
|
+
export const defaultLoadingScreenFadeMs = 1000;
|
|
10
12
|
/**
|
|
11
13
|
* Duration in milliseconds for the progress bar grow transition.
|
|
12
14
|
*
|
|
@@ -20,10 +22,16 @@ export const loadingScreenProgressGrowMs = 200;
|
|
|
20
22
|
*/
|
|
21
23
|
export const AnthaAssetLoadingScreen = defineElement()({
|
|
22
24
|
tagName: 'antha-asset-loading-screen',
|
|
25
|
+
cssVars: {
|
|
26
|
+
'antha-asset-loading-screen-fade-ms': addSuffix({
|
|
27
|
+
value: defaultLoadingScreenFadeMs,
|
|
28
|
+
suffix: 'ms',
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
23
31
|
hostClasses: {
|
|
24
32
|
'antha-asset-loading-screen-completed': ({ inputs }) => inputs.completed,
|
|
25
33
|
},
|
|
26
|
-
styles: ({ hostClasses }) => {
|
|
34
|
+
styles: ({ cssVars, hostClasses }) => {
|
|
27
35
|
return css `
|
|
28
36
|
:host {
|
|
29
37
|
position: fixed;
|
|
@@ -37,7 +45,7 @@ export const AnthaAssetLoadingScreen = defineElement()({
|
|
|
37
45
|
z-index: 9999;
|
|
38
46
|
gap: 24px;
|
|
39
47
|
opacity: 1;
|
|
40
|
-
transition: opacity ${
|
|
48
|
+
transition: opacity ${cssVars['antha-asset-loading-screen-fade-ms'].value} ease-in;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
.loading-text {
|
|
@@ -75,7 +83,15 @@ export const AnthaAssetLoadingScreen = defineElement()({
|
|
|
75
83
|
}
|
|
76
84
|
`;
|
|
77
85
|
},
|
|
78
|
-
render({ inputs }) {
|
|
86
|
+
render({ host, inputs, cssVars }) {
|
|
87
|
+
setCssVarValue({
|
|
88
|
+
forCssVar: cssVars['antha-asset-loading-screen-fade-ms'],
|
|
89
|
+
onElement: host,
|
|
90
|
+
toValue: addSuffix({
|
|
91
|
+
value: inputs.loadingScreenFadeMs,
|
|
92
|
+
suffix: 'ms',
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
79
95
|
const dotCount = inputs.dotCount % 4;
|
|
80
96
|
const dots = '.'.repeat(dotCount) + '\u00A0'.repeat(3 - dotCount);
|
|
81
97
|
return html `
|
|
@@ -114,64 +130,41 @@ export const anthaAssetModName = 'antha-asset';
|
|
|
114
130
|
* @category Pre-Built Mods
|
|
115
131
|
*/
|
|
116
132
|
export function createAnthaAssetMod(options = {}) {
|
|
133
|
+
const configuredLoadingScreenFadeMs = options.loadingScreenFadeMs ?? defaultLoadingScreenFadeMs;
|
|
117
134
|
return defineAnthaMod({
|
|
118
135
|
modName: anthaAssetModName,
|
|
119
136
|
async cleanup({ state }) {
|
|
120
137
|
await state.assetLoader?.destroy();
|
|
121
|
-
state.loadingScreenState = undefined;
|
|
122
|
-
state.isShowingLoadingScreen = false;
|
|
123
138
|
},
|
|
124
139
|
execute({ state, engine }) {
|
|
125
140
|
if (!state.assetLoader) {
|
|
126
141
|
state.assetLoader = new AssetLoader({
|
|
127
142
|
logger: engine.log,
|
|
128
143
|
});
|
|
129
|
-
if (!options.hideLoadingScreen) {
|
|
130
|
-
state.assetLoader.listen(AssetLoaderProgressUpdateEvent, (event) => {
|
|
131
|
-
if (event.detail.complete) {
|
|
132
|
-
state.loadingScreenState = {
|
|
133
|
-
current: 1,
|
|
134
|
-
total: 1,
|
|
135
|
-
currentResourceName: event.detail.currentResourceName ||
|
|
136
|
-
state.loadingScreenState?.currentResourceName,
|
|
137
|
-
completedAt: engine.totalMs,
|
|
138
|
-
};
|
|
139
|
-
state.isShowingLoadingScreen = false;
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
state.isShowingLoadingScreen = true;
|
|
143
|
-
state.loadingScreenState = {
|
|
144
|
-
current: event.detail.current,
|
|
145
|
-
total: event.detail.total,
|
|
146
|
-
currentResourceName: event.detail.currentResourceName,
|
|
147
|
-
completedAt: undefined,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
144
|
}
|
|
145
|
+
state.assetLoader.advanceLoadState({
|
|
146
|
+
currentTick: engine.currentTick,
|
|
147
|
+
totalMs: engine.totalMs,
|
|
148
|
+
});
|
|
153
149
|
if (options.hideLoadingScreen) {
|
|
154
150
|
return;
|
|
155
151
|
}
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const progressPercent =
|
|
161
|
-
? (state.loadingScreenState.current / state.loadingScreenState.total) * 100
|
|
162
|
-
: 0;
|
|
152
|
+
const loadState = state.assetLoader.loadState;
|
|
153
|
+
if (loadState &&
|
|
154
|
+
(loadState.completedAt == undefined ||
|
|
155
|
+
engine.totalMs <= loadState.completedAt + configuredLoadingScreenFadeMs)) {
|
|
156
|
+
const progressPercent = loadState.total > 0 ? (loadState.current / loadState.total) * 100 : 0;
|
|
163
157
|
return html `
|
|
164
158
|
<${AnthaAssetLoadingScreen.assign({
|
|
165
159
|
progressPercent,
|
|
166
160
|
dotCount: Math.floor(engine.totalMs / 500) % 4,
|
|
167
|
-
completed:
|
|
168
|
-
currentResourceName:
|
|
161
|
+
completed: loadState.completedAt != undefined,
|
|
162
|
+
currentResourceName: loadState.currentResourceName,
|
|
163
|
+
loadingScreenFadeMs: configuredLoadingScreenFadeMs,
|
|
169
164
|
})}></${AnthaAssetLoadingScreen}>
|
|
170
165
|
`;
|
|
171
166
|
}
|
|
172
|
-
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
167
|
+
return undefined;
|
|
175
168
|
},
|
|
176
169
|
});
|
|
177
170
|
}
|
package/dist/asset-loader.d.ts
CHANGED
|
@@ -79,12 +79,8 @@ export type AssetBulkLoaderLoadOptions = PartialWithUndefined<{
|
|
|
79
79
|
* @default false
|
|
80
80
|
*/
|
|
81
81
|
doNotUnload: boolean;
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
*
|
|
85
|
-
* @default false
|
|
86
|
-
*/
|
|
87
|
-
hideLoadingScreen: boolean;
|
|
82
|
+
/** Receives this bulk load's progress. */
|
|
83
|
+
loadSession: AssetLoadSession;
|
|
88
84
|
}>;
|
|
89
85
|
/**
|
|
90
86
|
* Options for {@link AssetLoader}.
|
|
@@ -98,75 +94,123 @@ export type AssetLoaderOptions = PartialWithUndefined<{
|
|
|
98
94
|
*/
|
|
99
95
|
logger: AnthaLogger;
|
|
100
96
|
}>;
|
|
101
|
-
|
|
97
|
+
/** Progress tracked by an {@link AssetLoadSession}. */
|
|
98
|
+
export type AssetLoadProgress = {
|
|
99
|
+
current: number;
|
|
100
|
+
total: number;
|
|
101
|
+
currentResourceName?: string | undefined;
|
|
102
|
+
};
|
|
103
|
+
/** State of the active asset load. */
|
|
104
|
+
export type AssetLoadState = AssetLoadProgress & {
|
|
105
|
+
completedAt: DOMHighResTimeStamp | undefined;
|
|
106
|
+
isLoading: boolean;
|
|
107
|
+
};
|
|
108
|
+
declare const AssetLoadSessionUpdateEvent_base: (new (eventInitDict: {
|
|
102
109
|
bubbles?: boolean;
|
|
103
110
|
cancelable?: boolean;
|
|
104
111
|
composed?: boolean;
|
|
105
|
-
detail: {
|
|
106
|
-
|
|
107
|
-
total: number;
|
|
108
|
-
currentResourceName?: string | undefined;
|
|
109
|
-
/**
|
|
110
|
-
* Always check this complete field first, as any misconfigured assets ma not correctly
|
|
111
|
-
* increment `total` but complete will always reliably mark the end of loading.
|
|
112
|
-
*/
|
|
112
|
+
detail: AssetLoadProgress & {
|
|
113
|
+
/** Indicates that the caller explicitly requested load completion. */
|
|
113
114
|
complete: boolean;
|
|
114
115
|
};
|
|
115
|
-
}) => import("typed-event-target").TypedCustomEvent<{
|
|
116
|
-
|
|
117
|
-
total: number;
|
|
118
|
-
currentResourceName?: string | undefined;
|
|
119
|
-
/**
|
|
120
|
-
* Always check this complete field first, as any misconfigured assets ma not correctly
|
|
121
|
-
* increment `total` but complete will always reliably mark the end of loading.
|
|
122
|
-
*/
|
|
116
|
+
}) => import("typed-event-target").TypedCustomEvent<AssetLoadProgress & {
|
|
117
|
+
/** Indicates that the caller explicitly requested load completion. */
|
|
123
118
|
complete: boolean;
|
|
124
|
-
}, "antha-asset-
|
|
119
|
+
}, "antha-asset-load-session-update-event">) & Pick<{
|
|
125
120
|
new (type: string, eventInitDict?: EventInit): Event;
|
|
126
121
|
prototype: Event;
|
|
127
122
|
readonly NONE: 0;
|
|
128
123
|
readonly CAPTURING_PHASE: 1;
|
|
129
124
|
readonly AT_TARGET: 2;
|
|
130
125
|
readonly BUBBLING_PHASE: 3;
|
|
131
|
-
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<{
|
|
132
|
-
|
|
133
|
-
total: number;
|
|
134
|
-
currentResourceName?: string | undefined;
|
|
135
|
-
/**
|
|
136
|
-
* Always check this complete field first, as any misconfigured assets ma not correctly
|
|
137
|
-
* increment `total` but complete will always reliably mark the end of loading.
|
|
138
|
-
*/
|
|
126
|
+
}, "prototype" | "NONE" | "CAPTURING_PHASE" | "AT_TARGET" | "BUBBLING_PHASE"> & Pick<import("typed-event-target").TypedCustomEvent<AssetLoadProgress & {
|
|
127
|
+
/** Indicates that the caller explicitly requested load completion. */
|
|
139
128
|
complete: boolean;
|
|
140
|
-
}, "antha-asset-
|
|
129
|
+
}, "antha-asset-load-session-update-event">, "type">;
|
|
130
|
+
/** Event dispatched when an {@link AssetLoadSession} progresses or completes. */
|
|
131
|
+
export declare class AssetLoadSessionUpdateEvent extends AssetLoadSessionUpdateEvent_base {
|
|
132
|
+
}
|
|
133
|
+
/** Manages the progress and explicit completion of an asset load. */
|
|
134
|
+
export declare class AssetLoadSession extends ListenTarget<AssetLoadSessionUpdateEvent> {
|
|
135
|
+
protected currentProgress: AssetLoadProgress;
|
|
136
|
+
protected isComplete: boolean;
|
|
137
|
+
/** Reports a load-progress update without completing the session. */
|
|
138
|
+
reportProgress(progress: Readonly<AssetLoadProgress>): void;
|
|
139
|
+
/** Adds progress to the active resource. */
|
|
140
|
+
incrementProgress({ amount, currentResourceName, }: Readonly<{
|
|
141
|
+
amount?: number | undefined;
|
|
142
|
+
currentResourceName: string;
|
|
143
|
+
}>): void;
|
|
144
|
+
/** Marks this asset load as complete. */
|
|
145
|
+
complete(): void;
|
|
146
|
+
}
|
|
141
147
|
/**
|
|
142
|
-
*
|
|
143
|
-
* loading screen progression.
|
|
148
|
+
* Maintains the active asset-load session and its state.
|
|
144
149
|
*
|
|
145
150
|
* @category Internal
|
|
146
151
|
*/
|
|
147
|
-
export declare class
|
|
152
|
+
export declare class AssetLoadSessionController {
|
|
153
|
+
protected currentLoadSessionInternal: AssetLoadSession;
|
|
154
|
+
protected loadStateInternal: AssetLoadState | undefined;
|
|
155
|
+
protected completionRequestedAtTick: number | undefined;
|
|
156
|
+
protected latestEngineTick: number;
|
|
157
|
+
/** Removes the listener for the active load session. */
|
|
158
|
+
protected removeLoadSessionListener: (() => boolean) | undefined;
|
|
159
|
+
constructor();
|
|
160
|
+
/** The active asset-load session. */
|
|
161
|
+
get currentLoadSession(): AssetLoadSession;
|
|
162
|
+
/** The active asset-load state. */
|
|
163
|
+
get loadState(): AssetLoadState | undefined;
|
|
164
|
+
/** Creates and activates a new asset-load session. */
|
|
165
|
+
createLoadSession(): AssetLoadSession;
|
|
166
|
+
/** Advances asset-load completion after an engine render. */
|
|
167
|
+
advance({ currentTick, totalMs, }: Readonly<{
|
|
168
|
+
currentTick: number;
|
|
169
|
+
totalMs: DOMHighResTimeStamp;
|
|
170
|
+
}>): void;
|
|
171
|
+
/** Stops tracking the active load session. */
|
|
172
|
+
destroy(): void;
|
|
173
|
+
/** Tracks updates from the active load session. */
|
|
174
|
+
protected listenToLoadSession(loadSession: AssetLoadSession): void;
|
|
148
175
|
}
|
|
149
176
|
/**
|
|
150
177
|
* Manages loading, caching, and cleanup of game assets with progress tracking.
|
|
151
178
|
*
|
|
152
179
|
* @category Asset
|
|
153
180
|
*/
|
|
154
|
-
export declare class AssetLoader
|
|
181
|
+
export declare class AssetLoader {
|
|
155
182
|
constructor(options?: Readonly<AssetLoaderOptions>);
|
|
156
183
|
/** Logs data. This will use the user's provided logger or default to browser logs. */
|
|
157
184
|
protected readonly log: AnthaLogger;
|
|
158
185
|
protected readonly assetCache: Map<Readonly<Asset<any>>, Promise<AssetLoaderResult<any>>>;
|
|
186
|
+
protected readonly loadSessionController: AssetLoadSessionController;
|
|
187
|
+
/** The active asset-load session. */
|
|
188
|
+
get currentLoadSession(): AssetLoadSession;
|
|
189
|
+
/** The active asset-load state. */
|
|
190
|
+
get loadState(): AssetLoadState | undefined;
|
|
191
|
+
/** Creates and activates a new asset-load session. */
|
|
192
|
+
createLoadSession(): AssetLoadSession;
|
|
193
|
+
/** Advances asset-load completion after an engine render. */
|
|
194
|
+
advanceLoadState({ currentTick, totalMs, }: Readonly<{
|
|
195
|
+
currentTick: number;
|
|
196
|
+
totalMs: DOMHighResTimeStamp;
|
|
197
|
+
}>): void;
|
|
159
198
|
/** Loads a single asset, returning its cached value if already loaded. */
|
|
160
|
-
loadIndividualAsset<ThisAsset extends Asset>({ asset, incrementProgressCallback, }: Readonly<{
|
|
199
|
+
loadIndividualAsset<ThisAsset extends Asset>({ asset, incrementProgressCallback, loadSession, }: Readonly<{
|
|
161
200
|
asset: Readonly<ThisAsset>;
|
|
162
201
|
incrementProgressCallback?: AssetIncrementProgressCallback | undefined;
|
|
202
|
+
loadSession?: AssetLoadSession | undefined;
|
|
163
203
|
}>): Promise<AssetValue<ThisAsset>>;
|
|
164
204
|
/** Runs cleanup callbacks for the given assets and removes them from the cache. */
|
|
165
205
|
unloadAssets(assets: ReadonlyArray<Asset>): Promise<void>;
|
|
206
|
+
/** Cleans up cached assets and stops tracking the active load session. */
|
|
166
207
|
destroy(): Promise<void>;
|
|
167
208
|
/** Loads multiple assets. */
|
|
168
209
|
bulkLoadAssets(assets: ReadonlyArray<Readonly<Asset>>, options?: Readonly<AssetBulkLoaderLoadOptions>): Promise<ReadonlyArray<unknown>>;
|
|
169
|
-
/**
|
|
170
|
-
protected
|
|
210
|
+
/** Sends load progress to the provided session. */
|
|
211
|
+
protected reportProgress({ loadSession, progress, }: Readonly<{
|
|
212
|
+
loadSession: AssetLoadSession | undefined;
|
|
213
|
+
progress: AssetLoadProgress;
|
|
214
|
+
}>): void;
|
|
171
215
|
}
|
|
172
216
|
export {};
|
package/dist/asset-loader.js
CHANGED
|
@@ -9,29 +9,161 @@ import { defineTypedCustomEvent, ListenTarget } from 'typed-event-target';
|
|
|
9
9
|
export function defineAsset(asset) {
|
|
10
10
|
return asset;
|
|
11
11
|
}
|
|
12
|
+
/** Event dispatched when an {@link AssetLoadSession} progresses or completes. */
|
|
13
|
+
export class AssetLoadSessionUpdateEvent extends defineTypedCustomEvent()('antha-asset-load-session-update-event') {
|
|
14
|
+
}
|
|
15
|
+
/** Manages the progress and explicit completion of an asset load. */
|
|
16
|
+
export class AssetLoadSession extends ListenTarget {
|
|
17
|
+
currentProgress = {
|
|
18
|
+
current: 0,
|
|
19
|
+
currentResourceName: undefined,
|
|
20
|
+
total: 0,
|
|
21
|
+
};
|
|
22
|
+
isComplete = false;
|
|
23
|
+
/** Reports a load-progress update without completing the session. */
|
|
24
|
+
reportProgress(progress) {
|
|
25
|
+
this.currentProgress = progress;
|
|
26
|
+
this.isComplete = false;
|
|
27
|
+
this.dispatch(new AssetLoadSessionUpdateEvent({
|
|
28
|
+
detail: {
|
|
29
|
+
...progress,
|
|
30
|
+
complete: false,
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
/** Adds progress to the active resource. */
|
|
35
|
+
incrementProgress({ amount, currentResourceName, }) {
|
|
36
|
+
this.reportProgress({
|
|
37
|
+
...this.currentProgress,
|
|
38
|
+
current: this.currentProgress.current + (amount ?? 1),
|
|
39
|
+
currentResourceName,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** Marks this asset load as complete. */
|
|
43
|
+
complete() {
|
|
44
|
+
if (this.isComplete) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.isComplete = true;
|
|
48
|
+
this.dispatch(new AssetLoadSessionUpdateEvent({
|
|
49
|
+
detail: {
|
|
50
|
+
...this.currentProgress,
|
|
51
|
+
complete: true,
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
12
56
|
/**
|
|
13
|
-
*
|
|
14
|
-
* loading screen progression.
|
|
57
|
+
* Maintains the active asset-load session and its state.
|
|
15
58
|
*
|
|
16
59
|
* @category Internal
|
|
17
60
|
*/
|
|
18
|
-
export class
|
|
61
|
+
export class AssetLoadSessionController {
|
|
62
|
+
currentLoadSessionInternal;
|
|
63
|
+
loadStateInternal;
|
|
64
|
+
completionRequestedAtTick;
|
|
65
|
+
latestEngineTick = 0;
|
|
66
|
+
/** Removes the listener for the active load session. */
|
|
67
|
+
removeLoadSessionListener;
|
|
68
|
+
constructor() {
|
|
69
|
+
const initialLoadSession = new AssetLoadSession();
|
|
70
|
+
this.currentLoadSessionInternal = initialLoadSession;
|
|
71
|
+
this.listenToLoadSession(initialLoadSession);
|
|
72
|
+
}
|
|
73
|
+
/** The active asset-load session. */
|
|
74
|
+
get currentLoadSession() {
|
|
75
|
+
return this.currentLoadSessionInternal;
|
|
76
|
+
}
|
|
77
|
+
/** The active asset-load state. */
|
|
78
|
+
get loadState() {
|
|
79
|
+
return this.loadStateInternal;
|
|
80
|
+
}
|
|
81
|
+
/** Creates and activates a new asset-load session. */
|
|
82
|
+
createLoadSession() {
|
|
83
|
+
const loadSession = new AssetLoadSession();
|
|
84
|
+
this.removeLoadSessionListener?.();
|
|
85
|
+
this.currentLoadSessionInternal = loadSession;
|
|
86
|
+
this.listenToLoadSession(loadSession);
|
|
87
|
+
loadSession.reportProgress({
|
|
88
|
+
current: 0,
|
|
89
|
+
total: 0,
|
|
90
|
+
});
|
|
91
|
+
return loadSession;
|
|
92
|
+
}
|
|
93
|
+
/** Advances asset-load completion after an engine render. */
|
|
94
|
+
advance({ currentTick, totalMs, }) {
|
|
95
|
+
this.latestEngineTick = currentTick + 1;
|
|
96
|
+
if (this.completionRequestedAtTick != undefined &&
|
|
97
|
+
this.completionRequestedAtTick < currentTick &&
|
|
98
|
+
this.loadStateInternal) {
|
|
99
|
+
this.completionRequestedAtTick = undefined;
|
|
100
|
+
this.loadStateInternal = {
|
|
101
|
+
...this.loadStateInternal,
|
|
102
|
+
completedAt: totalMs,
|
|
103
|
+
isLoading: false,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Stops tracking the active load session. */
|
|
108
|
+
destroy() {
|
|
109
|
+
this.removeLoadSessionListener?.();
|
|
110
|
+
this.currentLoadSessionInternal.destroy();
|
|
111
|
+
this.completionRequestedAtTick = undefined;
|
|
112
|
+
this.loadStateInternal = undefined;
|
|
113
|
+
}
|
|
114
|
+
/** Tracks updates from the active load session. */
|
|
115
|
+
listenToLoadSession(loadSession) {
|
|
116
|
+
this.removeLoadSessionListener = loadSession.listen(AssetLoadSessionUpdateEvent, (event) => {
|
|
117
|
+
if (event.detail.complete) {
|
|
118
|
+
this.completionRequestedAtTick = this.latestEngineTick;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.completionRequestedAtTick = undefined;
|
|
122
|
+
this.loadStateInternal = {
|
|
123
|
+
current: event.detail.current,
|
|
124
|
+
currentResourceName: event.detail.currentResourceName,
|
|
125
|
+
total: event.detail.total,
|
|
126
|
+
completedAt: undefined,
|
|
127
|
+
isLoading: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
19
132
|
}
|
|
20
133
|
/**
|
|
21
134
|
* Manages loading, caching, and cleanup of game assets with progress tracking.
|
|
22
135
|
*
|
|
23
136
|
* @category Asset
|
|
24
137
|
*/
|
|
25
|
-
export class AssetLoader
|
|
138
|
+
export class AssetLoader {
|
|
26
139
|
constructor(options = {}) {
|
|
27
|
-
super();
|
|
28
140
|
this.log = options.logger || browserAnthaLogger;
|
|
29
141
|
}
|
|
30
142
|
/** Logs data. This will use the user's provided logger or default to browser logs. */
|
|
31
143
|
log;
|
|
32
144
|
assetCache = new Map();
|
|
145
|
+
loadSessionController = new AssetLoadSessionController();
|
|
146
|
+
/** The active asset-load session. */
|
|
147
|
+
get currentLoadSession() {
|
|
148
|
+
return this.loadSessionController.currentLoadSession;
|
|
149
|
+
}
|
|
150
|
+
/** The active asset-load state. */
|
|
151
|
+
get loadState() {
|
|
152
|
+
return this.loadSessionController.loadState;
|
|
153
|
+
}
|
|
154
|
+
/** Creates and activates a new asset-load session. */
|
|
155
|
+
createLoadSession() {
|
|
156
|
+
return this.loadSessionController.createLoadSession();
|
|
157
|
+
}
|
|
158
|
+
/** Advances asset-load completion after an engine render. */
|
|
159
|
+
advanceLoadState({ currentTick, totalMs, }) {
|
|
160
|
+
this.loadSessionController.advance({
|
|
161
|
+
currentTick,
|
|
162
|
+
totalMs,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
33
165
|
/** Loads a single asset, returning its cached value if already loaded. */
|
|
34
|
-
async loadIndividualAsset({ asset, incrementProgressCallback, }) {
|
|
166
|
+
async loadIndividualAsset({ asset, incrementProgressCallback, loadSession, }) {
|
|
35
167
|
const cached = this.assetCache.get(asset);
|
|
36
168
|
if (cached) {
|
|
37
169
|
const assetResult = await cached;
|
|
@@ -39,9 +171,18 @@ export class AssetLoader extends ListenTarget {
|
|
|
39
171
|
}
|
|
40
172
|
const deferredLoadPromise = new DeferredPromise();
|
|
41
173
|
this.assetCache.set(asset, deferredLoadPromise.promise);
|
|
174
|
+
loadSession?.reportProgress({
|
|
175
|
+
current: 0,
|
|
176
|
+
currentResourceName: asset.name,
|
|
177
|
+
total: asset.maxProgress,
|
|
178
|
+
});
|
|
42
179
|
const loadedAsset = await asset.load({
|
|
43
180
|
incrementProgressCallback(progressParams) {
|
|
44
181
|
incrementProgressCallback?.(progressParams);
|
|
182
|
+
loadSession?.incrementProgress({
|
|
183
|
+
amount: progressParams,
|
|
184
|
+
currentResourceName: asset.name,
|
|
185
|
+
});
|
|
45
186
|
},
|
|
46
187
|
});
|
|
47
188
|
deferredLoadPromise.resolve(loadedAsset);
|
|
@@ -62,8 +203,9 @@ export class AssetLoader extends ListenTarget {
|
|
|
62
203
|
this.assetCache.delete(asset);
|
|
63
204
|
});
|
|
64
205
|
}
|
|
206
|
+
/** Cleans up cached assets and stops tracking the active load session. */
|
|
65
207
|
async destroy() {
|
|
66
|
-
|
|
208
|
+
this.loadSessionController.destroy();
|
|
67
209
|
const entries = Array.from(this.assetCache.entries());
|
|
68
210
|
await awaitedForEach(entries, async ([asset, result,]) => {
|
|
69
211
|
await (await result).cleanup?.();
|
|
@@ -85,12 +227,14 @@ export class AssetLoader extends ListenTarget {
|
|
|
85
227
|
return count + asset.maxProgress;
|
|
86
228
|
}, 0) + cleanupCount;
|
|
87
229
|
let currentProgress = 0;
|
|
88
|
-
if (
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
230
|
+
if (assetsToLoad.length) {
|
|
231
|
+
this.reportProgress({
|
|
232
|
+
loadSession: options.loadSession,
|
|
233
|
+
progress: {
|
|
234
|
+
current: currentProgress,
|
|
235
|
+
total: maxProgress,
|
|
236
|
+
currentResourceName: assetsToLoad[0]?.name,
|
|
237
|
+
},
|
|
94
238
|
});
|
|
95
239
|
}
|
|
96
240
|
await this.unloadAssets(assetsToCleanup);
|
|
@@ -98,14 +242,14 @@ export class AssetLoader extends ListenTarget {
|
|
|
98
242
|
return assets.map((asset) => this.assetCache.get(asset));
|
|
99
243
|
}
|
|
100
244
|
currentProgress += cleanupCount;
|
|
101
|
-
|
|
102
|
-
|
|
245
|
+
this.reportProgress({
|
|
246
|
+
loadSession: options.loadSession,
|
|
247
|
+
progress: {
|
|
103
248
|
current: currentProgress,
|
|
104
249
|
total: maxProgress,
|
|
105
250
|
currentResourceName: assetsToLoad[0]?.name,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
251
|
+
},
|
|
252
|
+
});
|
|
109
253
|
const chunkedAssets = options.maxParallelism
|
|
110
254
|
? chunkArray(assets, {
|
|
111
255
|
chunkSize: options.maxParallelism,
|
|
@@ -114,14 +258,14 @@ export class AssetLoader extends ListenTarget {
|
|
|
114
258
|
const createIncrementProgressCallback = (asset) => {
|
|
115
259
|
return (amount) => {
|
|
116
260
|
currentProgress += amount ?? 1;
|
|
117
|
-
|
|
118
|
-
|
|
261
|
+
this.reportProgress({
|
|
262
|
+
loadSession: options.loadSession,
|
|
263
|
+
progress: {
|
|
119
264
|
current: currentProgress,
|
|
120
265
|
total: maxProgress,
|
|
121
266
|
currentResourceName: asset.name,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
267
|
+
},
|
|
268
|
+
});
|
|
125
269
|
};
|
|
126
270
|
};
|
|
127
271
|
const results = (await awaitedBlockingMap(chunkedAssets, async (assetChunk) => {
|
|
@@ -129,14 +273,14 @@ export class AssetLoader extends ListenTarget {
|
|
|
129
273
|
if (this.assetCache.has(asset)) {
|
|
130
274
|
return (await this.assetCache.get(asset))?.value;
|
|
131
275
|
}
|
|
132
|
-
|
|
133
|
-
|
|
276
|
+
this.reportProgress({
|
|
277
|
+
loadSession: options.loadSession,
|
|
278
|
+
progress: {
|
|
134
279
|
current: currentProgress,
|
|
135
280
|
total: maxProgress,
|
|
136
281
|
currentResourceName: asset.name,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
282
|
+
},
|
|
283
|
+
});
|
|
140
284
|
return await this.loadIndividualAsset({
|
|
141
285
|
incrementProgressCallback: createIncrementProgressCallback(asset),
|
|
142
286
|
asset,
|
|
@@ -156,20 +300,10 @@ export class AssetLoader extends ListenTarget {
|
|
|
156
300
|
},
|
|
157
301
|
});
|
|
158
302
|
}
|
|
159
|
-
if (!options.hideLoadingScreen) {
|
|
160
|
-
this.dispatchProgressUpdate({
|
|
161
|
-
current: maxProgress,
|
|
162
|
-
total: maxProgress,
|
|
163
|
-
currentResourceName: assetsToLoad[assetsToLoad.length - 1]?.name,
|
|
164
|
-
complete: true,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
303
|
return results;
|
|
168
304
|
}
|
|
169
|
-
/**
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
detail,
|
|
173
|
-
}));
|
|
305
|
+
/** Sends load progress to the provided session. */
|
|
306
|
+
reportProgress({ loadSession, progress, }) {
|
|
307
|
+
loadSession?.reportProgress(progress);
|
|
174
308
|
}
|
|
175
309
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antha/asset",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "An Antha mod for handling asset loading.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vir",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@augment-vir/assert": "^32.2.3",
|
|
38
38
|
"@augment-vir/common": "^32.2.3",
|
|
39
|
+
"lit-css-vars": "^3.6.3",
|
|
39
40
|
"typed-event-target": "^4.3.3"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
"pixi.js": "^8.19.0"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
|
-
"@antha/engine": "^0.
|
|
51
|
+
"@antha/engine": "^0.4.0",
|
|
51
52
|
"element-vir": ">=26"
|
|
52
53
|
},
|
|
53
54
|
"engines": {
|