forked from 4ian/GDevelop
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathpixi-spine-atlas-manager.ts
More file actions
174 lines (153 loc) 路 5.71 KB
/
Copy pathpixi-spine-atlas-manager.ts
File metadata and controls
174 lines (153 loc) 路 5.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*
* GDevelop JS Platform
* Copyright 2013-present Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
const atlasKinds: ResourceKind[] = ['atlas'];
/**
* SpineAtlasManager loads `.atlas` files via the official `@esotericsoftware/spine-pixi-v7`
* Pixi atlas loader, sharing texture pages with the engine's ImageManager.
*
* The loader is auto-registered by the `spine-pixi-v7` IIFE bundle. We simply prepare
* the asset metadata (`data.images`) so that PIXI.Assets binds atlas pages to the
* already-loaded base textures instead of fetching them again.
*
* @category Resources > Spine
*/
export class SpineAtlasManager implements gdjs.ResourceManager {
private _imageManager: ImageManager;
private _resourceLoader: ResourceLoader;
private _loadedSpineAtlases = new gdjs.ResourceCache<spine.TextureAtlas>();
private _loadingSpineAtlases = new gdjs.ResourceCache<
Promise<spine.TextureAtlas>
>();
/**
* @param resourceLoader The resources loader of the game.
* @param imageManager The image manager of the game.
*/
constructor(
resourceLoader: gdjs.ResourceLoader,
imageManager: ImageManager
) {
this._resourceLoader = resourceLoader;
this._imageManager = imageManager;
}
getResourceKinds(): ResourceKind[] {
return atlasKinds;
}
async processResource(_resourceName: string): Promise<void> {
// The spine-pixi-v7 atlas loader parses the resource itself.
}
async loadResource(resourceName: string): Promise<void> {
await this.getOrLoad(resourceName);
}
/**
* Returns a cached promise resolving to the loaded atlas, loading it if needed.
*/
getOrLoad(resourceName: string): Promise<spine.TextureAtlas> {
const resource = this._getAtlasResource(resourceName);
if (!resource) {
return Promise.reject(
new Error(`Unable to find atlas for resource '${resourceName}'.`)
);
}
const cachedAtlas = this._loadedSpineAtlases.get(resource);
if (cachedAtlas) {
return Promise.resolve(cachedAtlas);
}
const inflight = this._loadingSpineAtlases.get(resource);
if (inflight) {
return inflight;
}
const loadingPromise = this._load(resource).then((atlas) => {
this._loadedSpineAtlases.set(resource, atlas);
return atlas;
});
this._loadingSpineAtlases.set(resource, loadingPromise);
return loadingPromise;
}
private async _load(resource: ResourceData): Promise<spine.TextureAtlas> {
const game = this._resourceLoader.getRuntimeGame();
const embeddedResourcesNames = game.getEmbeddedResourcesNames(
resource.name
);
if (!embeddedResourcesNames.length) {
throw new Error(`${resource.name} does not have image metadata!`);
}
const images = embeddedResourcesNames.reduce<{
[key: string]: PIXI.BaseTexture;
}>((imagesMap, embeddedResourceName) => {
const mappedResourceName = game.resolveEmbeddedResource(
resource.name,
embeddedResourceName
);
// The v7 atlas loader expects BaseTexture instances when sharing pages
// with already-loaded textures.
imagesMap[embeddedResourceName] =
this._imageManager.getOrLoadPIXITexture(
mappedResourceName
).baseTexture;
return imagesMap;
}, {});
const url = this._resourceLoader.getFullUrl(resource.file);
const alias = url;
PIXI.Assets.setPreferences({
preferWorkers: false,
crossOrigin: this._resourceLoader.checkIfCredentialsRequired(url)
? 'use-credentials'
: 'anonymous',
});
PIXI.Assets.add({ alias, src: url, data: { images } });
return PIXI.Assets.load<spine.TextureAtlas>(alias);
}
/**
* Check if the given atlas resource was loaded.
* @param resourceName The name of the atlas resource.
*/
isLoaded(resourceName: string): boolean {
return !!this._loadedSpineAtlases.getFromName(resourceName);
}
/**
* Returns the alias used to register the atlas in PIXI.Assets,
* or null if the resource is not loaded.
*/
getAtlasAlias(resourceName: string): string | null {
const resource = this._getAtlasResource(resourceName);
if (!resource) return null;
return this._loadedSpineAtlases.get(resource)
? this._resourceLoader.getFullUrl(resource.file)
: null;
}
/**
* Returns the loaded TextureAtlas for the given resource, if available.
*/
getAtlasTexture(resourceName: string): spine.TextureAtlas | null {
return this._loadedSpineAtlases.getFromName(resourceName);
}
private _getAtlasResource(resourceName: string): ResourceData | null {
const resource = this._resourceLoader.getResource(resourceName);
return resource && this.getResourceKinds().includes(resource.kind)
? resource
: null;
}
/**
* To be called when the game is disposed.
*/
dispose(): void {
this._loadedSpineAtlases.clear();
this._loadingSpineAtlases.clear();
}
unloadResource(resourceData: ResourceData): void {
const resource = this._getAtlasResource(resourceData.name);
// PIXI.Assets.unload disposes the TextureAtlas and clears the cache entry,
// preventing a stale atlas from being reused on the next load.
if (resource) {
const alias = this._resourceLoader.getFullUrl(resource.file);
PIXI.Assets.unload(alias).catch(() => {});
}
this._loadedSpineAtlases.delete(resourceData);
this._loadingSpineAtlases.delete(resourceData);
}
}
}