@nimbus2d/core 0.0.1
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/dist/index.mjs +18433 -0
- package/package.json +39 -0
- package/src/base/EventEmitter.js +187 -0
- package/src/base/Subsystem.js +183 -0
- package/src/base/index.js +6 -0
- package/src/core/Application/AppState.js +27 -0
- package/src/core/Application/Application.js +480 -0
- package/src/core/Application/DefaultConfig.js +63 -0
- package/src/core/Application/index.js +7 -0
- package/src/core/AssetManager/AssetManager.js +711 -0
- package/src/core/AssetManager/AssetTypes.js +108 -0
- package/src/core/AssetManager/adapters/IResourceAdapter.js +50 -0
- package/src/core/AssetManager/adapters/PixiAdapter.js +90 -0
- package/src/core/AssetManager/core/RefCounter.js +167 -0
- package/src/core/AssetManager/core/ResourceCache.js +281 -0
- package/src/core/AssetManager/core/ResourceEntry.js +191 -0
- package/src/core/AssetManager/core/ResourceEventBus.js +127 -0
- package/src/core/AssetManager/handlers/IResourceHandler.js +45 -0
- package/src/core/AssetManager/index.js +26 -0
- package/src/core/AssetManager/plugins/IResourcePlugin.js +56 -0
- package/src/core/Camera/Camera.js +534 -0
- package/src/core/Camera/CameraTypes.js +59 -0
- package/src/core/Camera/index.js +13 -0
- package/src/core/ConfigSystem/ConfigLayer.js +230 -0
- package/src/core/ConfigSystem/ConfigLayerType.js +31 -0
- package/src/core/ConfigSystem/ConfigMerger.js +50 -0
- package/src/core/ConfigSystem/ConfigPath.js +96 -0
- package/src/core/ConfigSystem/ConfigSystem.js +378 -0
- package/src/core/ConfigSystem/ConfigTypes.js +29 -0
- package/src/core/ConfigSystem/ConfigWatcher.js +84 -0
- package/src/core/ConfigSystem/index.js +19 -0
- package/src/core/EventBus/EventBus.js +797 -0
- package/src/core/EventBus/Interceptor.js +26 -0
- package/src/core/EventBus/PerformanceStats.js +105 -0
- package/src/core/EventBus/index.js +7 -0
- package/src/core/Lifecycle/Lifecycle.js +265 -0
- package/src/core/Lifecycle/LifecycleState.js +31 -0
- package/src/core/Lifecycle/index.js +6 -0
- package/src/core/Logger/ConsoleHandler.js +55 -0
- package/src/core/Logger/LogHandler.js +34 -0
- package/src/core/Logger/LogLevel.js +38 -0
- package/src/core/Logger/Logger.js +234 -0
- package/src/core/Logger/index.js +8 -0
- package/src/core/PluginSystem/Plugin.js +156 -0
- package/src/core/PluginSystem/PluginEntry.js +102 -0
- package/src/core/PluginSystem/PluginState.js +30 -0
- package/src/core/PluginSystem/PluginSystem.js +530 -0
- package/src/core/PluginSystem/index.js +8 -0
- package/src/core/PoolManager/DefaultConfig.js +19 -0
- package/src/core/PoolManager/ObjectPool.js +326 -0
- package/src/core/PoolManager/PoolManager.js +253 -0
- package/src/core/PoolManager/PoolTypes.js +92 -0
- package/src/core/PoolManager/index.js +15 -0
- package/src/core/SceneManager/FadeTransition.js +64 -0
- package/src/core/SceneManager/MaskTransition.js +234 -0
- package/src/core/SceneManager/Scene.js +221 -0
- package/src/core/SceneManager/SceneManager.js +658 -0
- package/src/core/SceneManager/SceneState.js +29 -0
- package/src/core/SceneManager/SlideTransition.js +113 -0
- package/src/core/SceneManager/Transition.js +102 -0
- package/src/core/SceneManager/ZoomTransition.js +119 -0
- package/src/core/SceneManager/index.js +12 -0
- package/src/core/SpriteGPULayer/SpriteGPULayer.js +321 -0
- package/src/core/SpriteGPULayer/SpriteGPULayerTypes.js +49 -0
- package/src/core/SpriteGPULayer/index.js +11 -0
- package/src/core/Timeline/Clip.js +101 -0
- package/src/core/Timeline/DefaultConfig.js +41 -0
- package/src/core/Timeline/Easing.js +176 -0
- package/src/core/Timeline/Timeline.js +458 -0
- package/src/core/Timeline/TimelineTypes.js +89 -0
- package/src/core/Timeline/Track.js +202 -0
- package/src/core/Timeline/index.js +22 -0
- package/src/errors/ConfigError.js +47 -0
- package/src/errors/LifecycleError.js +58 -0
- package/src/errors/NimbusError.js +43 -0
- package/src/errors/PluginError.js +138 -0
- package/src/errors/ResourceError.js +166 -0
- package/src/errors/SceneError.js +124 -0
- package/src/errors/SubsystemError.js +68 -0
- package/src/errors/index.js +53 -0
- package/src/index.js +86 -0
- package/src/types/index.js +141 -0
- package/src/utils/MemoryChecker.js +374 -0
- package/src/utils/Validator.js +60 -0
- package/src/utils/index.js +84 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Nimbus2D - AssetManager 资源管理
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// 职责:
|
|
5
|
+
// 1. 门面 + 适配器(IResourceAdapter → PixiAdapter)+ 处理器 + 插件钩子
|
|
6
|
+
// 2. 引用计数 + 缓存(分组+标签)+ 独立 EventBus
|
|
7
|
+
// 3. 8 种资源类型:IMAGE/SPRITESHEET/ATLAS/AUDIO/FONT/DATA/BINARY/SHADER
|
|
8
|
+
// 4. PixiAdapter 核心内置自动注册(支持 6 种类型)
|
|
9
|
+
// 5. 3 种加载策略:LAZY/EAGER/GROUP
|
|
10
|
+
// 6. 双通道事件广播(内部 EventBus + 核心 EventBus)
|
|
11
|
+
//
|
|
12
|
+
// 继承关系:
|
|
13
|
+
// EventEmitter → Subsystem → AssetManager(Lifecycle 内第二个初始化)
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
import { Subsystem } from '../../base/Subsystem.js'
|
|
17
|
+
import { ResourceCache } from './core/ResourceCache.js'
|
|
18
|
+
import { RefCounter } from './core/RefCounter.js'
|
|
19
|
+
import { ResourceEventBus } from './core/ResourceEventBus.js'
|
|
20
|
+
import { ResourceEntry } from './core/ResourceEntry.js'
|
|
21
|
+
import { PixiAdapter } from './adapters/PixiAdapter.js'
|
|
22
|
+
import {
|
|
23
|
+
ResourceType,
|
|
24
|
+
ResourceState,
|
|
25
|
+
LoadStrategy,
|
|
26
|
+
ResourceEventType,
|
|
27
|
+
ResourceNotFoundError,
|
|
28
|
+
ResourceDuplicateError,
|
|
29
|
+
ResourceStateError,
|
|
30
|
+
ResourceLoadError,
|
|
31
|
+
AdapterError,
|
|
32
|
+
ResourceDestroyedError
|
|
33
|
+
} from './AssetTypes.js'
|
|
34
|
+
|
|
35
|
+
class AssetManager extends Subsystem {
|
|
36
|
+
/**
|
|
37
|
+
* 构造函数
|
|
38
|
+
* @param {import('../EventBus/EventBus.js').EventBus} eventBus - 全局事件总线
|
|
39
|
+
* @param {import('../ConfigSystem/ConfigSystem.js').ConfigSystem} config - 配置系统实例
|
|
40
|
+
* @param {import('../Logger/Logger.js').Logger} logger - 日志系统实例
|
|
41
|
+
* @param {object} pixi - PixiJS Application 实例(用于 PixiAdapter 自动注册)
|
|
42
|
+
*/
|
|
43
|
+
constructor(eventBus, config, logger, pixi) {
|
|
44
|
+
super(eventBus, config, logger)
|
|
45
|
+
|
|
46
|
+
/** @type {object} PixiJS Application 实例 */
|
|
47
|
+
this._pixi = pixi
|
|
48
|
+
|
|
49
|
+
/** @type {ResourceCache} 资源缓存 */
|
|
50
|
+
this._cache = new ResourceCache()
|
|
51
|
+
|
|
52
|
+
/** @type {RefCounter} 引用计数器 */
|
|
53
|
+
this._refCounter = new RefCounter()
|
|
54
|
+
|
|
55
|
+
/** @type {ResourceEventBus} 内部事件总线 */
|
|
56
|
+
this._internalBus = new ResourceEventBus()
|
|
57
|
+
|
|
58
|
+
/** @type {Map<string, import('./adapters/IResourceAdapter.js').IResourceAdapter>} 适配器映射(type → adapter) */
|
|
59
|
+
this._adapters = new Map()
|
|
60
|
+
|
|
61
|
+
/** @type {import('./handlers/IResourceHandler.js').IResourceHandler[]} 处理器列表 */
|
|
62
|
+
this._handlers = []
|
|
63
|
+
|
|
64
|
+
/** @type {import('./plugins/IResourcePlugin.js').IResourcePlugin[]} 插件列表 */
|
|
65
|
+
this._plugins = []
|
|
66
|
+
|
|
67
|
+
/** @type {Set<string>} 加载中的资源 ID(防止重复加载) */
|
|
68
|
+
this._loadingIds = new Set()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// --------------------------------------------------------------------------
|
|
72
|
+
// 适配器管理
|
|
73
|
+
// --------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 注册适配器
|
|
77
|
+
* @param {string} type - 资源类型(ResourceType 枚举值)
|
|
78
|
+
* @param {import('./adapters/IResourceAdapter.js').IResourceAdapter} adapter - 适配器实例
|
|
79
|
+
*/
|
|
80
|
+
registerAdapter(type, adapter) {
|
|
81
|
+
if (this._isDestroyed) throw new ResourceDestroyedError()
|
|
82
|
+
this._adapters.set(type, adapter)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 注销适配器
|
|
87
|
+
* @param {string} type - 资源类型
|
|
88
|
+
* @returns {boolean} 是否注销成功
|
|
89
|
+
*/
|
|
90
|
+
unregisterAdapter(type) {
|
|
91
|
+
return this._adapters.delete(type)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --------------------------------------------------------------------------
|
|
95
|
+
// 处理器管理
|
|
96
|
+
// --------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 添加处理器
|
|
100
|
+
* @param {import('./handlers/IResourceHandler.js').IResourceHandler} handler - 处理器实例
|
|
101
|
+
*/
|
|
102
|
+
addHandler(handler) {
|
|
103
|
+
if (!this._handlers.includes(handler)) {
|
|
104
|
+
this._handlers.push(handler)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 移除处理器
|
|
110
|
+
* @param {import('./handlers/IResourceHandler.js').IResourceHandler} handler - 处理器实例
|
|
111
|
+
*/
|
|
112
|
+
removeHandler(handler) {
|
|
113
|
+
const index = this._handlers.indexOf(handler)
|
|
114
|
+
if (index !== -1) {
|
|
115
|
+
this._handlers.splice(index, 1)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// --------------------------------------------------------------------------
|
|
120
|
+
// 资源插件管理
|
|
121
|
+
// --------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 添加资源插件
|
|
125
|
+
* @param {import('./plugins/IResourcePlugin.js').IResourcePlugin} plugin - 资源插件实例
|
|
126
|
+
*/
|
|
127
|
+
addPlugin(plugin) {
|
|
128
|
+
if (!this._plugins.includes(plugin)) {
|
|
129
|
+
this._plugins.push(plugin)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 移除资源插件
|
|
135
|
+
* @param {import('./plugins/IResourcePlugin.js').IResourcePlugin} plugin - 资源插件实例
|
|
136
|
+
*/
|
|
137
|
+
removePlugin(plugin) {
|
|
138
|
+
const index = this._plugins.indexOf(plugin)
|
|
139
|
+
if (index !== -1) {
|
|
140
|
+
this._plugins.splice(index, 1)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// --------------------------------------------------------------------------
|
|
145
|
+
// 资源注册
|
|
146
|
+
// --------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 注册资源
|
|
150
|
+
* @param {string} id - 资源唯一标识
|
|
151
|
+
* @param {Object} options - 注册选项
|
|
152
|
+
* @param {string} options.type - 资源类型(ResourceType 枚举值)
|
|
153
|
+
* @param {string} options.src - 资源路径
|
|
154
|
+
* @param {string} [options.strategy=LoadStrategy.LAZY] - 加载策略
|
|
155
|
+
* @param {string} [options.group] - 分组名称
|
|
156
|
+
* @param {string[]} [options.tags=[]] - 标签列表
|
|
157
|
+
* @param {Object} [options.adapterOptions={}] - 适配器选项
|
|
158
|
+
*/
|
|
159
|
+
register(id, options) {
|
|
160
|
+
if (this._isDestroyed) throw new ResourceDestroyedError()
|
|
161
|
+
|
|
162
|
+
// 检查重复
|
|
163
|
+
if (this._cache.has(id)) {
|
|
164
|
+
throw new ResourceDuplicateError(id)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 创建资源条目
|
|
168
|
+
const entry = new ResourceEntry(id, options)
|
|
169
|
+
this._cache.add(entry)
|
|
170
|
+
|
|
171
|
+
// 广播事件
|
|
172
|
+
this._emitEvent(ResourceEventType.REGISTERED, { id, entry })
|
|
173
|
+
|
|
174
|
+
// EAGER 策略:注册后立即加载
|
|
175
|
+
if (entry.strategy === LoadStrategy.EAGER) {
|
|
176
|
+
// 异步加载不等待,错误通过事件通知
|
|
177
|
+
this.load(id).catch(() => {})
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 注销资源
|
|
183
|
+
* @param {string} id - 资源 ID
|
|
184
|
+
*/
|
|
185
|
+
unregister(id) {
|
|
186
|
+
if (this._isDestroyed) throw new ResourceDestroyedError()
|
|
187
|
+
|
|
188
|
+
const entry = this._cache.get(id)
|
|
189
|
+
if (!entry) return
|
|
190
|
+
|
|
191
|
+
// 已加载的资源需先卸载
|
|
192
|
+
if (entry.isLoaded) {
|
|
193
|
+
this._doUnload(entry)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 移除引用计数记录
|
|
197
|
+
this._refCounter.removeResource(id)
|
|
198
|
+
|
|
199
|
+
// 从缓存移除
|
|
200
|
+
this._cache.delete(id)
|
|
201
|
+
|
|
202
|
+
// 广播事件
|
|
203
|
+
this._emitEvent(ResourceEventType.UNREGISTERED, { id })
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --------------------------------------------------------------------------
|
|
207
|
+
// 资源加载
|
|
208
|
+
// --------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 加载单个资源
|
|
212
|
+
* @param {string} id - 资源 ID
|
|
213
|
+
* @returns {Promise<*>} - 资源数据
|
|
214
|
+
*/
|
|
215
|
+
async load(id) {
|
|
216
|
+
if (this._isDestroyed) throw new ResourceDestroyedError()
|
|
217
|
+
|
|
218
|
+
const entry = this._getEntryOrThrow(id)
|
|
219
|
+
|
|
220
|
+
// 已加载直接返回
|
|
221
|
+
if (entry.isLoaded) return entry.data
|
|
222
|
+
|
|
223
|
+
// 正在加载中,等待完成
|
|
224
|
+
if (entry.state === ResourceState.LOADING) {
|
|
225
|
+
return this._waitForLoad(id)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 获取适配器
|
|
229
|
+
const adapter = this._getAdapterOrThrow(entry.type)
|
|
230
|
+
|
|
231
|
+
// 标记加载中
|
|
232
|
+
entry.setLoading()
|
|
233
|
+
this._loadingIds.add(id)
|
|
234
|
+
this._emitEvent(ResourceEventType.LOAD_START, { id })
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
// 执行 beforeLoad 插件钩子
|
|
238
|
+
let modifiedEntry = entry
|
|
239
|
+
for (const plugin of this._plugins) {
|
|
240
|
+
modifiedEntry = await plugin.beforeLoad(modifiedEntry)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 通过适配器加载
|
|
244
|
+
const data = await adapter.load(modifiedEntry)
|
|
245
|
+
|
|
246
|
+
// 执行 afterLoad 插件钩子
|
|
247
|
+
let modifiedData = data
|
|
248
|
+
for (const plugin of this._plugins) {
|
|
249
|
+
modifiedData = await plugin.afterLoad(modifiedEntry, modifiedData)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 标记已加载
|
|
253
|
+
entry.setLoaded(modifiedData)
|
|
254
|
+
|
|
255
|
+
// 通知处理器
|
|
256
|
+
for (const handler of this._handlers) {
|
|
257
|
+
try {
|
|
258
|
+
handler.onLoadComplete(entry, modifiedData)
|
|
259
|
+
} catch (e) {
|
|
260
|
+
// 处理器错误不影响加载流程
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 广播事件
|
|
265
|
+
this._emitEvent(ResourceEventType.LOAD_COMPLETE, { id, data: modifiedData })
|
|
266
|
+
|
|
267
|
+
return modifiedData
|
|
268
|
+
} catch (e) {
|
|
269
|
+
// 标记加载失败
|
|
270
|
+
const error = e instanceof Error ? e : new Error(String(e))
|
|
271
|
+
entry.setError(error)
|
|
272
|
+
|
|
273
|
+
// 通知处理器
|
|
274
|
+
for (const handler of this._handlers) {
|
|
275
|
+
try {
|
|
276
|
+
handler.onLoadError(entry, error)
|
|
277
|
+
} catch (handlerErr) {
|
|
278
|
+
// 处理器错误不影响错误流程
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 广播事件
|
|
283
|
+
this._emitEvent(ResourceEventType.LOAD_ERROR, { id, error })
|
|
284
|
+
this._emitEvent(ResourceEventType.ERROR, { id, error })
|
|
285
|
+
|
|
286
|
+
throw new ResourceLoadError(id, error)
|
|
287
|
+
} finally {
|
|
288
|
+
this._loadingIds.delete(id)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 加载多个资源
|
|
294
|
+
* @param {string[]} ids - 资源 ID 列表
|
|
295
|
+
* @returns {Promise<Map<string, *>>} - 资源数据映射(id → data)
|
|
296
|
+
*/
|
|
297
|
+
async loadAll(ids) {
|
|
298
|
+
const results = new Map()
|
|
299
|
+
const promises = ids.map(async (id) => {
|
|
300
|
+
const data = await this.load(id)
|
|
301
|
+
results.set(id, data)
|
|
302
|
+
})
|
|
303
|
+
await Promise.all(promises)
|
|
304
|
+
return results
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 加载分组
|
|
309
|
+
* @param {string} groupName - 分组名称
|
|
310
|
+
* @returns {Promise<Map<string, *>>} - 资源数据映射
|
|
311
|
+
*/
|
|
312
|
+
async loadGroup(groupName) {
|
|
313
|
+
const ids = this._cache.getGroupIds(groupName)
|
|
314
|
+
return this.loadAll(ids)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// --------------------------------------------------------------------------
|
|
318
|
+
// 资源卸载
|
|
319
|
+
// --------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 卸载单个资源
|
|
323
|
+
* @param {string} id - 资源 ID
|
|
324
|
+
* @returns {Promise<void>}
|
|
325
|
+
*/
|
|
326
|
+
async unload(id) {
|
|
327
|
+
if (this._isDestroyed) throw new ResourceDestroyedError()
|
|
328
|
+
|
|
329
|
+
const entry = this._cache.get(id)
|
|
330
|
+
if (!entry) return
|
|
331
|
+
|
|
332
|
+
// 引用计数 > 0 时不卸载
|
|
333
|
+
if (this._refCounter.getCount(id) > 0) {
|
|
334
|
+
this._safeLog('warn', `资源 ${id} 引用计数 > 0,跳过卸载`)
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
await this._doUnload(entry)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 卸载分组
|
|
343
|
+
* @param {string} groupName - 分组名称
|
|
344
|
+
* @returns {Promise<void>}
|
|
345
|
+
*/
|
|
346
|
+
async unloadGroup(groupName) {
|
|
347
|
+
const entries = this._cache.getGroupEntries(groupName)
|
|
348
|
+
for (const entry of entries) {
|
|
349
|
+
if (this._refCounter.getCount(entry.id) === 0) {
|
|
350
|
+
await this._doUnload(entry)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* 清空所有资源
|
|
357
|
+
* @returns {Promise<void>}
|
|
358
|
+
*/
|
|
359
|
+
async clear() {
|
|
360
|
+
const entries = this._cache.getAllEntries()
|
|
361
|
+
for (const entry of entries) {
|
|
362
|
+
try {
|
|
363
|
+
if (entry.isLoaded) {
|
|
364
|
+
await this._doUnload(entry)
|
|
365
|
+
}
|
|
366
|
+
} catch (e) {
|
|
367
|
+
// 清空阶段错误不中断
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
this._cache.clear()
|
|
371
|
+
this._refCounter.clear()
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// --------------------------------------------------------------------------
|
|
375
|
+
// 引用计数
|
|
376
|
+
// --------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* 引用资源(异步,LAZY 策略时触发加载)
|
|
380
|
+
* @param {string} id - 资源 ID
|
|
381
|
+
* @param {string} holder - 持有者标识(如场景名)
|
|
382
|
+
* @returns {Promise<*>} - 资源数据
|
|
383
|
+
*/
|
|
384
|
+
async retain(id, holder) {
|
|
385
|
+
if (this._isDestroyed) throw new ResourceDestroyedError()
|
|
386
|
+
|
|
387
|
+
this._getEntryOrThrow(id)
|
|
388
|
+
|
|
389
|
+
// 增加引用计数
|
|
390
|
+
this._refCounter.retain(id, holder)
|
|
391
|
+
|
|
392
|
+
// 广播事件
|
|
393
|
+
this._emitEvent(ResourceEventType.RETAIN, { id, holder })
|
|
394
|
+
|
|
395
|
+
// 如果未加载,触发加载
|
|
396
|
+
const entry = this._cache.get(id)
|
|
397
|
+
if (entry && !entry.isLoaded && entry.state !== ResourceState.LOADING) {
|
|
398
|
+
return this.load(id)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return entry ? entry.data : null
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* 释放引用
|
|
406
|
+
* @param {string} id - 资源 ID
|
|
407
|
+
* @param {string} holder - 持有者标识
|
|
408
|
+
*/
|
|
409
|
+
release(id, holder) {
|
|
410
|
+
if (this._isDestroyed) return
|
|
411
|
+
|
|
412
|
+
const newCount = this._refCounter.release(id, holder)
|
|
413
|
+
|
|
414
|
+
// 广播事件
|
|
415
|
+
this._emitEvent(ResourceEventType.RELEASE, { id, holder })
|
|
416
|
+
|
|
417
|
+
// 引用计数为 0 时,可根据配置决定是否自动卸载
|
|
418
|
+
// 目前不自动卸载,由用户手动调用 unload 或 unloadGroup
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* 释放 holder 的所有引用
|
|
423
|
+
* @param {string} holder - 持有者标识
|
|
424
|
+
*/
|
|
425
|
+
releaseAllByHolder(holder) {
|
|
426
|
+
this._refCounter.releaseAllByHolder(holder)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// --------------------------------------------------------------------------
|
|
430
|
+
// 分组管理
|
|
431
|
+
// --------------------------------------------------------------------------
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* 创建分组
|
|
435
|
+
* @param {string} name - 分组名称
|
|
436
|
+
* @param {string[]} [ids=[]] - 初始资源 ID 列表
|
|
437
|
+
*/
|
|
438
|
+
createGroup(name, ids = []) {
|
|
439
|
+
this._cache.createGroup(name, ids)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* 添加到分组
|
|
444
|
+
* @param {string} name - 分组名称
|
|
445
|
+
* @param {string} id - 资源 ID
|
|
446
|
+
*/
|
|
447
|
+
addToGroup(name, id) {
|
|
448
|
+
this._cache.addToGroup(name, id)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* 从分组移除
|
|
453
|
+
* @param {string} name - 分组名称
|
|
454
|
+
* @param {string} id - 资源 ID
|
|
455
|
+
*/
|
|
456
|
+
removeFromGroup(name, id) {
|
|
457
|
+
this._cache.removeFromGroup(name, id)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// --------------------------------------------------------------------------
|
|
461
|
+
// 查询方法
|
|
462
|
+
// --------------------------------------------------------------------------
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* 获取资源数据
|
|
466
|
+
* @param {string} id - 资源 ID
|
|
467
|
+
* @returns {*|null} 资源数据,未加载返回 null
|
|
468
|
+
*/
|
|
469
|
+
get(id) {
|
|
470
|
+
const entry = this._cache.get(id)
|
|
471
|
+
return entry && entry.isLoaded ? entry.data : null
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* 是否已注册
|
|
476
|
+
* @param {string} id - 资源 ID
|
|
477
|
+
* @returns {boolean}
|
|
478
|
+
*/
|
|
479
|
+
has(id) {
|
|
480
|
+
return this._cache.has(id)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* 获取资源状态
|
|
485
|
+
* @param {string} id - 资源 ID
|
|
486
|
+
* @returns {string|null} ResourceState 枚举值,未找到返回 null
|
|
487
|
+
*/
|
|
488
|
+
getState(id) {
|
|
489
|
+
const entry = this._cache.get(id)
|
|
490
|
+
return entry ? entry.state : null
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* 获取资源条目
|
|
495
|
+
* @param {string} id - 资源 ID
|
|
496
|
+
* @returns {ResourceEntry|null}
|
|
497
|
+
*/
|
|
498
|
+
getEntry(id) {
|
|
499
|
+
return this._cache.get(id)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* 获取统计信息
|
|
504
|
+
* @returns {Object}
|
|
505
|
+
*/
|
|
506
|
+
getStats() {
|
|
507
|
+
return this._cache.getStats()
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// --------------------------------------------------------------------------
|
|
511
|
+
// 生命周期(继承自 Subsystem)
|
|
512
|
+
// --------------------------------------------------------------------------
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* 初始化:自动注册 PixiAdapter
|
|
516
|
+
* @returns {Promise<void>}
|
|
517
|
+
*/
|
|
518
|
+
async initialize() {
|
|
519
|
+
await super.initialize()
|
|
520
|
+
|
|
521
|
+
// 自动注册 PixiAdapter(核心内置)
|
|
522
|
+
if (this._pixi) {
|
|
523
|
+
const pixiAdapter = new PixiAdapter(this._pixi)
|
|
524
|
+
// 为 PixiAdapter 支持的 6 种类型各注册一次(通过公共 getter 访问)
|
|
525
|
+
for (const type of pixiAdapter.supportedTypes) {
|
|
526
|
+
this._adapters.set(type, pixiAdapter)
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* 销毁:清理所有资源
|
|
533
|
+
* @returns {Promise<void>}
|
|
534
|
+
*/
|
|
535
|
+
async destroy() {
|
|
536
|
+
if (this._isDestroyed) return
|
|
537
|
+
|
|
538
|
+
// 清理所有资源(错误不中断)
|
|
539
|
+
try {
|
|
540
|
+
await this.clear()
|
|
541
|
+
} catch (e) {
|
|
542
|
+
// 忽略清理错误
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// 清理适配器
|
|
546
|
+
this._adapters.clear()
|
|
547
|
+
|
|
548
|
+
// 清理处理器和插件
|
|
549
|
+
this._handlers = []
|
|
550
|
+
this._plugins = []
|
|
551
|
+
|
|
552
|
+
// 销毁内部事件总线
|
|
553
|
+
this._internalBus.destroy()
|
|
554
|
+
this._internalBus = null
|
|
555
|
+
|
|
556
|
+
// 清理缓存和引用计数
|
|
557
|
+
this._cache.clear()
|
|
558
|
+
this._refCounter.clear()
|
|
559
|
+
|
|
560
|
+
this._pixi = null
|
|
561
|
+
this._loadingIds.clear()
|
|
562
|
+
|
|
563
|
+
await super.destroy()
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// --------------------------------------------------------------------------
|
|
567
|
+
// 私有方法
|
|
568
|
+
// --------------------------------------------------------------------------
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* 获取资源条目,不存在则抛错
|
|
572
|
+
* @param {string} id - 资源 ID
|
|
573
|
+
* @returns {ResourceEntry}
|
|
574
|
+
* @throws {ResourceNotFoundError}
|
|
575
|
+
* @private
|
|
576
|
+
*/
|
|
577
|
+
_getEntryOrThrow(id) {
|
|
578
|
+
const entry = this._cache.get(id)
|
|
579
|
+
if (!entry) {
|
|
580
|
+
throw new ResourceNotFoundError(id)
|
|
581
|
+
}
|
|
582
|
+
return entry
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* 获取适配器,不存在则抛错
|
|
587
|
+
* @param {string} type - 资源类型
|
|
588
|
+
* @returns {import('./adapters/IResourceAdapter.js').IResourceAdapter}
|
|
589
|
+
* @throws {AdapterError}
|
|
590
|
+
* @private
|
|
591
|
+
*/
|
|
592
|
+
_getAdapterOrThrow(type) {
|
|
593
|
+
const adapter = this._adapters.get(type)
|
|
594
|
+
if (!adapter) {
|
|
595
|
+
throw new AdapterError(`未找到资源类型 ${type} 的适配器`)
|
|
596
|
+
}
|
|
597
|
+
return adapter
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* 执行卸载逻辑
|
|
602
|
+
* @param {ResourceEntry} entry - 资源条目
|
|
603
|
+
* @returns {Promise<void>}
|
|
604
|
+
* @private
|
|
605
|
+
*/
|
|
606
|
+
async _doUnload(entry) {
|
|
607
|
+
if (!entry.isLoaded) return
|
|
608
|
+
|
|
609
|
+
const adapter = this._adapters.get(entry.type)
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
// 执行 beforeUnload 插件钩子
|
|
613
|
+
for (const plugin of this._plugins) {
|
|
614
|
+
plugin.beforeUnload(entry)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 通过适配器卸载
|
|
618
|
+
if (adapter) {
|
|
619
|
+
await adapter.unload(entry)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// 执行 afterUnload 插件钩子
|
|
623
|
+
for (const plugin of this._plugins) {
|
|
624
|
+
plugin.afterUnload(entry)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// 标记已卸载
|
|
628
|
+
entry.setUnloaded()
|
|
629
|
+
|
|
630
|
+
// 通知处理器
|
|
631
|
+
for (const handler of this._handlers) {
|
|
632
|
+
try {
|
|
633
|
+
handler.onUnload(entry)
|
|
634
|
+
} catch (e) {
|
|
635
|
+
// 处理器错误不影响卸载流程
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// 广播事件
|
|
640
|
+
this._emitEvent(ResourceEventType.UNLOAD, { id: entry.id })
|
|
641
|
+
} catch (e) {
|
|
642
|
+
// 卸载错误记录但不中断
|
|
643
|
+
this._safeLog('error', `资源 ${entry.id} 卸载失败`, { error: e })
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* 等待资源加载完成
|
|
649
|
+
* @param {string} id - 资源 ID
|
|
650
|
+
* @returns {Promise<*>} - 资源数据
|
|
651
|
+
* @private
|
|
652
|
+
*/
|
|
653
|
+
_waitForLoad(id) {
|
|
654
|
+
return new Promise((resolve, reject) => {
|
|
655
|
+
// 监听加载完成或失败
|
|
656
|
+
const offComplete = this._internalBus.on(ResourceEventType.LOAD_COMPLETE, (payload) => {
|
|
657
|
+
if (payload.id === id) {
|
|
658
|
+
offComplete()
|
|
659
|
+
offError()
|
|
660
|
+
resolve(payload.data)
|
|
661
|
+
}
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
const offError = this._internalBus.on(ResourceEventType.LOAD_ERROR, (payload) => {
|
|
665
|
+
if (payload.id === id) {
|
|
666
|
+
offComplete()
|
|
667
|
+
offError()
|
|
668
|
+
reject(new ResourceLoadError(id, payload.error))
|
|
669
|
+
}
|
|
670
|
+
})
|
|
671
|
+
})
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* 双通道事件广播
|
|
676
|
+
* @param {string} eventType - 事件类型
|
|
677
|
+
* @param {Object} payload - 事件数据
|
|
678
|
+
* @private
|
|
679
|
+
*/
|
|
680
|
+
_emitEvent(eventType, payload) {
|
|
681
|
+
// 内部通道
|
|
682
|
+
this._internalBus.emit(eventType, payload)
|
|
683
|
+
// 核心通道(带 asset: 前缀)
|
|
684
|
+
if (this._eventBus) {
|
|
685
|
+
try {
|
|
686
|
+
this._eventBus.emit(`asset:${eventType}`, payload)
|
|
687
|
+
} catch (e) {
|
|
688
|
+
// EventBus 错误不影响资源管理流程
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* 防御性日志方法
|
|
695
|
+
* @param {string} level - 日志级别
|
|
696
|
+
* @param {string} message - 日志消息
|
|
697
|
+
* @param {Object} [context={}] - 上下文
|
|
698
|
+
* @private
|
|
699
|
+
*/
|
|
700
|
+
_safeLog(level, message, context = {}) {
|
|
701
|
+
try {
|
|
702
|
+
if (this._logger && !this._logger.isDestroyed) {
|
|
703
|
+
this._logger[level](message, context)
|
|
704
|
+
}
|
|
705
|
+
} catch (e) {
|
|
706
|
+
// 完全忽略日志错误
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
export { AssetManager }
|