@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.
Files changed (85) hide show
  1. package/dist/index.mjs +18433 -0
  2. package/package.json +39 -0
  3. package/src/base/EventEmitter.js +187 -0
  4. package/src/base/Subsystem.js +183 -0
  5. package/src/base/index.js +6 -0
  6. package/src/core/Application/AppState.js +27 -0
  7. package/src/core/Application/Application.js +480 -0
  8. package/src/core/Application/DefaultConfig.js +63 -0
  9. package/src/core/Application/index.js +7 -0
  10. package/src/core/AssetManager/AssetManager.js +711 -0
  11. package/src/core/AssetManager/AssetTypes.js +108 -0
  12. package/src/core/AssetManager/adapters/IResourceAdapter.js +50 -0
  13. package/src/core/AssetManager/adapters/PixiAdapter.js +90 -0
  14. package/src/core/AssetManager/core/RefCounter.js +167 -0
  15. package/src/core/AssetManager/core/ResourceCache.js +281 -0
  16. package/src/core/AssetManager/core/ResourceEntry.js +191 -0
  17. package/src/core/AssetManager/core/ResourceEventBus.js +127 -0
  18. package/src/core/AssetManager/handlers/IResourceHandler.js +45 -0
  19. package/src/core/AssetManager/index.js +26 -0
  20. package/src/core/AssetManager/plugins/IResourcePlugin.js +56 -0
  21. package/src/core/Camera/Camera.js +534 -0
  22. package/src/core/Camera/CameraTypes.js +59 -0
  23. package/src/core/Camera/index.js +13 -0
  24. package/src/core/ConfigSystem/ConfigLayer.js +230 -0
  25. package/src/core/ConfigSystem/ConfigLayerType.js +31 -0
  26. package/src/core/ConfigSystem/ConfigMerger.js +50 -0
  27. package/src/core/ConfigSystem/ConfigPath.js +96 -0
  28. package/src/core/ConfigSystem/ConfigSystem.js +378 -0
  29. package/src/core/ConfigSystem/ConfigTypes.js +29 -0
  30. package/src/core/ConfigSystem/ConfigWatcher.js +84 -0
  31. package/src/core/ConfigSystem/index.js +19 -0
  32. package/src/core/EventBus/EventBus.js +797 -0
  33. package/src/core/EventBus/Interceptor.js +26 -0
  34. package/src/core/EventBus/PerformanceStats.js +105 -0
  35. package/src/core/EventBus/index.js +7 -0
  36. package/src/core/Lifecycle/Lifecycle.js +265 -0
  37. package/src/core/Lifecycle/LifecycleState.js +31 -0
  38. package/src/core/Lifecycle/index.js +6 -0
  39. package/src/core/Logger/ConsoleHandler.js +55 -0
  40. package/src/core/Logger/LogHandler.js +34 -0
  41. package/src/core/Logger/LogLevel.js +38 -0
  42. package/src/core/Logger/Logger.js +234 -0
  43. package/src/core/Logger/index.js +8 -0
  44. package/src/core/PluginSystem/Plugin.js +156 -0
  45. package/src/core/PluginSystem/PluginEntry.js +102 -0
  46. package/src/core/PluginSystem/PluginState.js +30 -0
  47. package/src/core/PluginSystem/PluginSystem.js +530 -0
  48. package/src/core/PluginSystem/index.js +8 -0
  49. package/src/core/PoolManager/DefaultConfig.js +19 -0
  50. package/src/core/PoolManager/ObjectPool.js +326 -0
  51. package/src/core/PoolManager/PoolManager.js +253 -0
  52. package/src/core/PoolManager/PoolTypes.js +92 -0
  53. package/src/core/PoolManager/index.js +15 -0
  54. package/src/core/SceneManager/FadeTransition.js +64 -0
  55. package/src/core/SceneManager/MaskTransition.js +234 -0
  56. package/src/core/SceneManager/Scene.js +221 -0
  57. package/src/core/SceneManager/SceneManager.js +658 -0
  58. package/src/core/SceneManager/SceneState.js +29 -0
  59. package/src/core/SceneManager/SlideTransition.js +113 -0
  60. package/src/core/SceneManager/Transition.js +102 -0
  61. package/src/core/SceneManager/ZoomTransition.js +119 -0
  62. package/src/core/SceneManager/index.js +12 -0
  63. package/src/core/SpriteGPULayer/SpriteGPULayer.js +321 -0
  64. package/src/core/SpriteGPULayer/SpriteGPULayerTypes.js +49 -0
  65. package/src/core/SpriteGPULayer/index.js +11 -0
  66. package/src/core/Timeline/Clip.js +101 -0
  67. package/src/core/Timeline/DefaultConfig.js +41 -0
  68. package/src/core/Timeline/Easing.js +176 -0
  69. package/src/core/Timeline/Timeline.js +458 -0
  70. package/src/core/Timeline/TimelineTypes.js +89 -0
  71. package/src/core/Timeline/Track.js +202 -0
  72. package/src/core/Timeline/index.js +22 -0
  73. package/src/errors/ConfigError.js +47 -0
  74. package/src/errors/LifecycleError.js +58 -0
  75. package/src/errors/NimbusError.js +43 -0
  76. package/src/errors/PluginError.js +138 -0
  77. package/src/errors/ResourceError.js +166 -0
  78. package/src/errors/SceneError.js +124 -0
  79. package/src/errors/SubsystemError.js +68 -0
  80. package/src/errors/index.js +53 -0
  81. package/src/index.js +86 -0
  82. package/src/types/index.js +141 -0
  83. package/src/utils/MemoryChecker.js +374 -0
  84. package/src/utils/Validator.js +60 -0
  85. package/src/utils/index.js +84 -0
@@ -0,0 +1,108 @@
1
+ // ============================================================================
2
+ // Nimbus2D - 资源类型定义
3
+ // ----------------------------------------------------------------------------
4
+ // 定义资源管理模块的所有枚举类型和错误类重导出。
5
+ //
6
+ // 包含:
7
+ // - ResourceType:资源类型枚举(8种)
8
+ // - ResourceState:资源状态枚举
9
+ // - LoadStrategy:加载策略枚举
10
+ // - ResourceEventType:事件类型枚举
11
+ // - 错误类重导出(从 errors/ResourceError.js)
12
+ // ============================================================================
13
+
14
+ /**
15
+ * 资源类型枚举
16
+ * @enum {string}
17
+ */
18
+ const ResourceType = {
19
+ /** 图片(PNG/JPG/WebP/GIF) */
20
+ IMAGE: 'IMAGE',
21
+ /** 精灵图集(含帧动画数据) */
22
+ SPRITESHEET: 'SPRITESHEET',
23
+ /** 纹理图集(含区域划分数据) */
24
+ ATLAS: 'ATLAS',
25
+ /** 音频(第一期 PixiAdapter 不支持,需自定义适配器) */
26
+ AUDIO: 'AUDIO',
27
+ /** 字体 */
28
+ FONT: 'FONT',
29
+ /** 数据(JSON/XML/CSV) */
30
+ DATA: 'DATA',
31
+ /** 二进制 */
32
+ BINARY: 'BINARY',
33
+ /** 着色器(第一期 PixiAdapter 不支持,需自定义适配器) */
34
+ SHADER: 'SHADER'
35
+ }
36
+
37
+ /**
38
+ * 资源状态枚举
39
+ * @enum {string}
40
+ */
41
+ const ResourceState = {
42
+ /** 已注册(未加载) */
43
+ REGISTERED: 'REGISTERED',
44
+ /** 加载中 */
45
+ LOADING: 'LOADING',
46
+ /** 已加载(可用) */
47
+ LOADED: 'LOADED',
48
+ /** 加载失败 */
49
+ ERROR: 'ERROR',
50
+ /** 已卸载 */
51
+ UNLOADED: 'UNLOADED'
52
+ }
53
+
54
+ /**
55
+ * 加载策略枚举
56
+ * @enum {string}
57
+ */
58
+ const LoadStrategy = {
59
+ /** 懒加载:retain 时触发加载(默认) */
60
+ LAZY: 'LAZY',
61
+ /** 急加载:register 后立即加载 */
62
+ EAGER: 'EAGER',
63
+ /** 分组加载:需手动调用 loadGroup */
64
+ GROUP: 'GROUP'
65
+ }
66
+
67
+ /**
68
+ * 资源事件类型枚举
69
+ * @enum {string}
70
+ */
71
+ const ResourceEventType = {
72
+ /** 资源已注册 */
73
+ REGISTERED: 'registered',
74
+ /** 资源已注销 */
75
+ UNREGISTERED: 'unregistered',
76
+ /** 加载开始 */
77
+ LOAD_START: 'load:start',
78
+ /** 加载完成 */
79
+ LOAD_COMPLETE: 'load:complete',
80
+ /** 加载失败 */
81
+ LOAD_ERROR: 'load:error',
82
+ /** 资源已卸载 */
83
+ UNLOAD: 'unload',
84
+ /** 资源被引用 */
85
+ RETAIN: 'retain',
86
+ /** 资源被释放 */
87
+ RELEASE: 'release',
88
+ /** 资源错误 */
89
+ ERROR: 'error'
90
+ }
91
+
92
+ // 重导出错误类(统一从 errors 模块导入)
93
+ export {
94
+ ResourceError,
95
+ ResourceNotFoundError,
96
+ ResourceDuplicateError,
97
+ ResourceStateError,
98
+ ResourceLoadError,
99
+ AdapterError,
100
+ ResourceDestroyedError
101
+ } from '../../errors/ResourceError.js'
102
+
103
+ export {
104
+ ResourceType,
105
+ ResourceState,
106
+ LoadStrategy,
107
+ ResourceEventType
108
+ }
@@ -0,0 +1,50 @@
1
+ // ============================================================================
2
+ // Nimbus2D - 资源适配器接口
3
+ // ----------------------------------------------------------------------------
4
+ // 定义资源适配器的契约,所有适配器必须实现此接口。
5
+ //
6
+ // 设计决策:
7
+ // - 独立设计(不继承任何类)
8
+ // - 三个方法:load、unload、supports
9
+ // - load 返回 Promise<any>,unload 返回 Promise<void>
10
+ // ============================================================================
11
+
12
+ /**
13
+ * 资源适配器接口(基类)
14
+ * 所有资源适配器必须继承此类并实现其方法。
15
+ *
16
+ * 子类需要实现:
17
+ * - load(entry): 加载资源
18
+ * - unload(entry): 卸载资源
19
+ * - supports(type): 检查是否支持指定类型
20
+ */
21
+ class IResourceAdapter {
22
+ /**
23
+ * 加载资源
24
+ * @param {import('../core/ResourceEntry.js').ResourceEntry} entry - 资源条目
25
+ * @returns {Promise<any>} - 资源数据
26
+ */
27
+ async load(entry) {
28
+ throw new Error('IResourceAdapter.load() 必须由子类实现')
29
+ }
30
+
31
+ /**
32
+ * 卸载资源
33
+ * @param {import('../core/ResourceEntry.js').ResourceEntry} entry - 资源条目
34
+ * @returns {Promise<void>}
35
+ */
36
+ async unload(entry) {
37
+ throw new Error('IResourceAdapter.unload() 必须由子类实现')
38
+ }
39
+
40
+ /**
41
+ * 检查适配器是否支持指定资源类型
42
+ * @param {string} type - ResourceType 枚举值
43
+ * @returns {boolean}
44
+ */
45
+ supports(type) {
46
+ throw new Error('IResourceAdapter.supports() 必须由子类实现')
47
+ }
48
+ }
49
+
50
+ export { IResourceAdapter }
@@ -0,0 +1,90 @@
1
+ // ============================================================================
2
+ // Nimbus2D - PixiJS 资源适配器
3
+ // ----------------------------------------------------------------------------
4
+ // 桥接 PixiJS v8 Assets,支持 6 种资源类型。
5
+ // 核心内置自动注册,无需用户手动添加。
6
+ //
7
+ // 支持的资源类型:
8
+ // IMAGE / SPRITESHEET / ATLAS / FONT / DATA / BINARY
9
+ //
10
+ // 不支持的类型(需用户自定义适配器):
11
+ // AUDIO / SHADER
12
+ // ============================================================================
13
+
14
+ import { Assets } from 'pixi.js'
15
+ import { IResourceAdapter } from './IResourceAdapter.js'
16
+ import { ResourceType } from '../AssetTypes.js'
17
+
18
+ class PixiAdapter extends IResourceAdapter {
19
+ /**
20
+ * 构造函数
21
+ * @param {object} pixi - PixiJS Application 实例
22
+ */
23
+ constructor(pixi) {
24
+ super()
25
+
26
+ /** @type {object} PixiJS Application 实例 */
27
+ this._pixi = pixi
28
+
29
+ /** @type {string[]} 支持的资源类型列表 */
30
+ this._supportedTypes = [
31
+ ResourceType.IMAGE,
32
+ ResourceType.SPRITESHEET,
33
+ ResourceType.ATLAS,
34
+ ResourceType.FONT,
35
+ ResourceType.DATA,
36
+ ResourceType.BINARY
37
+ ]
38
+ }
39
+
40
+ /**
41
+ * 加载资源(桥接 PixiJS v8 Assets.load)
42
+ * v8 签名:Assets.load(url) 或 Assets.load({ src, data, alias })
43
+ * @param {import('../core/ResourceEntry.js').ResourceEntry} entry - 资源条目
44
+ * @returns {Promise<any>} - 资源数据
45
+ */
46
+ async load(entry) {
47
+ // PixiJS v8 Assets.load 支持字符串或 LoadAsset 对象
48
+ const loadOptions = entry.adapterOptions || {}
49
+
50
+ // 构建加载参数
51
+ const loadAsset = { src: entry.src }
52
+ if (loadOptions.pixiData) {
53
+ loadAsset.data = loadOptions.pixiData
54
+ }
55
+ if (entry.alias) {
56
+ loadAsset.alias = entry.alias
57
+ }
58
+
59
+ return await Assets.load(loadAsset)
60
+ }
61
+
62
+ /**
63
+ * 卸载资源(桥接 PixiJS v8 Assets.unload)
64
+ * @param {import('../core/ResourceEntry.js').ResourceEntry} entry - 资源条目
65
+ * @returns {Promise<void>}
66
+ */
67
+ async unload(entry) {
68
+ // PixiJS v8 Assets.unload 按资源标识卸载
69
+ await Assets.unload(entry.src)
70
+ }
71
+
72
+ /**
73
+ * 检查是否支持指定资源类型
74
+ * @param {string} type - ResourceType 枚举值
75
+ * @returns {boolean}
76
+ */
77
+ supports(type) {
78
+ return this._supportedTypes.includes(type)
79
+ }
80
+
81
+ /**
82
+ * 支持的资源类型列表(只读)
83
+ * @type {string[]}
84
+ */
85
+ get supportedTypes() {
86
+ return this._supportedTypes
87
+ }
88
+ }
89
+
90
+ export { PixiAdapter }
@@ -0,0 +1,167 @@
1
+ // ============================================================================
2
+ // Nimbus2D - 引用计数器
3
+ // ----------------------------------------------------------------------------
4
+ // 管理资源的引用计数,支持按持有者(holder)追踪引用关系。
5
+ //
6
+ // 设计决策:
7
+ // - 主存储:Map<resourceId, number> 全局引用计数
8
+ // - 持有者映射:Map<holder, Map<resourceId, number>> 按持有者追踪
9
+ // - 支持按持有者批量释放
10
+ // ============================================================================
11
+
12
+ class RefCounter {
13
+ /**
14
+ * 构造函数
15
+ */
16
+ constructor() {
17
+ /** @type {Map<string, number>} 全局引用计数(resourceId → count) */
18
+ this._counts = new Map()
19
+
20
+ /** @type {Map<string, Map<string, number>>} 持有者映射(holder → (resourceId → count)) */
21
+ this._holders = new Map()
22
+ }
23
+
24
+ // --------------------------------------------------------------------------
25
+ // 引用操作
26
+ // --------------------------------------------------------------------------
27
+
28
+ /**
29
+ * 增加引用计数
30
+ * @param {string} resourceId - 资源 ID
31
+ * @param {string} holder - 持有者标识
32
+ * @returns {number} 新的全局引用计数
33
+ */
34
+ retain(resourceId, holder) {
35
+ // 增加全局计数
36
+ const count = (this._counts.get(resourceId) || 0) + 1
37
+ this._counts.set(resourceId, count)
38
+
39
+ // 增加持有者计数
40
+ if (!this._holders.has(holder)) {
41
+ this._holders.set(holder, new Map())
42
+ }
43
+ const holderMap = this._holders.get(holder)
44
+ holderMap.set(resourceId, (holderMap.get(resourceId) || 0) + 1)
45
+
46
+ return count
47
+ }
48
+
49
+ /**
50
+ * 减少引用计数
51
+ * @param {string} resourceId - 资源 ID
52
+ * @param {string} holder - 持有者标识
53
+ * @returns {number} 新的全局引用计数(最小为 0)
54
+ */
55
+ release(resourceId, holder) {
56
+ let count = this._counts.get(resourceId) || 0
57
+
58
+ // 减少持有者计数
59
+ const holderMap = this._holders.get(holder)
60
+ if (holderMap && holderMap.has(resourceId)) {
61
+ const holderCount = holderMap.get(resourceId) - 1
62
+ if (holderCount <= 0) {
63
+ holderMap.delete(resourceId)
64
+ if (holderMap.size === 0) {
65
+ this._holders.delete(holder)
66
+ }
67
+ } else {
68
+ holderMap.set(resourceId, holderCount)
69
+ }
70
+
71
+ // 减少全局计数
72
+ count = Math.max(0, count - 1)
73
+ this._counts.set(resourceId, count)
74
+ }
75
+
76
+ return count
77
+ }
78
+
79
+ /**
80
+ * 释放持有者的所有引用
81
+ * @param {string} holder - 持有者标识
82
+ * @returns {string[]} 引用计数降为 0 的资源 ID 列表(可安全卸载)
83
+ */
84
+ releaseAllByHolder(holder) {
85
+ const holderMap = this._holders.get(holder)
86
+ if (!holderMap) return []
87
+
88
+ /** @type {string[]} 引用计数降为 0 的资源列表 */
89
+ const zeroRefIds = []
90
+
91
+ for (const [resourceId, holderCount] of holderMap) {
92
+ // 按持有者的引用数减少全局计数(持有者可能对同一资源多次 retain)
93
+ const count = Math.max(0, (this._counts.get(resourceId) || 0) - holderCount)
94
+ this._counts.set(resourceId, count)
95
+
96
+ if (count === 0) {
97
+ zeroRefIds.push(resourceId)
98
+ }
99
+ }
100
+
101
+ // 清空持有者映射
102
+ this._holders.delete(holder)
103
+
104
+ return zeroRefIds
105
+ }
106
+
107
+ // --------------------------------------------------------------------------
108
+ // 查询方法
109
+ // --------------------------------------------------------------------------
110
+
111
+ /**
112
+ * 获取资源的全局引用计数
113
+ * @param {string} resourceId - 资源 ID
114
+ * @returns {number}
115
+ */
116
+ getCount(resourceId) {
117
+ return this._counts.get(resourceId) || 0
118
+ }
119
+
120
+ /**
121
+ * 获取持有者对指定资源的引用计数
122
+ * @param {string} resourceId - 资源 ID
123
+ * @param {string} holder - 持有者标识
124
+ * @returns {number}
125
+ */
126
+ getHolderCount(resourceId, holder) {
127
+ const holderMap = this._holders.get(holder)
128
+ if (!holderMap) return 0
129
+ return holderMap.get(resourceId) || 0
130
+ }
131
+
132
+ /**
133
+ * 获取持有者引用的所有资源 ID
134
+ * @param {string} holder - 持有者标识
135
+ * @returns {string[]}
136
+ */
137
+ getHolderResources(holder) {
138
+ const holderMap = this._holders.get(holder)
139
+ return holderMap ? [...holderMap.keys()] : []
140
+ }
141
+
142
+ // --------------------------------------------------------------------------
143
+ // 清理
144
+ // --------------------------------------------------------------------------
145
+
146
+ /**
147
+ * 清空所有引用计数
148
+ */
149
+ clear() {
150
+ this._counts.clear()
151
+ this._holders.clear()
152
+ }
153
+
154
+ /**
155
+ * 移除资源记录(资源被卸载时调用)
156
+ * @param {string} resourceId - 资源 ID
157
+ */
158
+ removeResource(resourceId) {
159
+ this._counts.delete(resourceId)
160
+ // 从所有持有者映射中移除
161
+ for (const holderMap of this._holders.values()) {
162
+ holderMap.delete(resourceId)
163
+ }
164
+ }
165
+ }
166
+
167
+ export { RefCounter }
@@ -0,0 +1,281 @@
1
+ // ============================================================================
2
+ // Nimbus2D - 资源缓存
3
+ // ----------------------------------------------------------------------------
4
+ // 管理资源条目的存储、分组和标签索引。
5
+ //
6
+ // 设计决策:
7
+ // - 主存储使用 Map<string, ResourceEntry>
8
+ // - 分组索引使用 Map<string, Set<string>>
9
+ // - 标签索引使用 Map<string, Set<string>>
10
+ // - 支持按分组/标签批量查询
11
+ // ============================================================================
12
+
13
+ import { ResourceState } from '../AssetTypes.js'
14
+
15
+ class ResourceCache {
16
+ /**
17
+ * 构造函数
18
+ */
19
+ constructor() {
20
+ /** @type {Map<string, import('../core/ResourceEntry.js').ResourceEntry>} 主存储(id → entry) */
21
+ this._entries = new Map()
22
+
23
+ /** @type {Map<string, Set<string>>} 分组索引(groupName → id集合) */
24
+ this._groups = new Map()
25
+
26
+ /** @type {Map<string, Set<string>>} 标签索引(tagName → id集合) */
27
+ this._tags = new Map()
28
+ }
29
+
30
+ // --------------------------------------------------------------------------
31
+ // 条目操作
32
+ // --------------------------------------------------------------------------
33
+
34
+ /**
35
+ * 添加资源条目
36
+ * @param {import('../core/ResourceEntry.js').ResourceEntry} entry - 资源条目
37
+ */
38
+ add(entry) {
39
+ this._entries.set(entry.id, entry)
40
+
41
+ // 索引分组
42
+ if (entry.group) {
43
+ if (!this._groups.has(entry.group)) {
44
+ this._groups.set(entry.group, new Set())
45
+ }
46
+ this._groups.get(entry.group).add(entry.id)
47
+ }
48
+
49
+ // 索引标签
50
+ for (const tag of entry.tags) {
51
+ if (!this._tags.has(tag)) {
52
+ this._tags.set(tag, new Set())
53
+ }
54
+ this._tags.get(tag).add(entry.id)
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 获取资源条目
60
+ * @param {string} id - 资源 ID
61
+ * @returns {import('../core/ResourceEntry.js').ResourceEntry|null}
62
+ */
63
+ get(id) {
64
+ return this._entries.get(id) || null
65
+ }
66
+
67
+ /**
68
+ * 是否存在资源
69
+ * @param {string} id - 资源 ID
70
+ * @returns {boolean}
71
+ */
72
+ has(id) {
73
+ return this._entries.has(id)
74
+ }
75
+
76
+ /**
77
+ * 删除资源条目
78
+ * @param {string} id - 资源 ID
79
+ * @returns {boolean} 是否删除成功
80
+ */
81
+ delete(id) {
82
+ const entry = this._entries.get(id)
83
+ if (!entry) return false
84
+
85
+ // 从分组索引中移除
86
+ if (entry.group && this._groups.has(entry.group)) {
87
+ this._groups.get(entry.group).delete(id)
88
+ // 空分组自动清理
89
+ if (this._groups.get(entry.group).size === 0) {
90
+ this._groups.delete(entry.group)
91
+ }
92
+ }
93
+
94
+ // 从标签索引中移除
95
+ for (const tag of entry.tags) {
96
+ if (this._tags.has(tag)) {
97
+ this._tags.get(tag).delete(id)
98
+ // 空标签自动清理
99
+ if (this._tags.get(tag).size === 0) {
100
+ this._tags.delete(tag)
101
+ }
102
+ }
103
+ }
104
+
105
+ return this._entries.delete(id)
106
+ }
107
+
108
+ // --------------------------------------------------------------------------
109
+ // 分组操作
110
+ // --------------------------------------------------------------------------
111
+
112
+ /**
113
+ * 创建分组
114
+ * @param {string} name - 分组名称
115
+ * @param {string[]} [ids=[]] - 初始资源 ID 列表
116
+ */
117
+ createGroup(name, ids = []) {
118
+ if (!this._groups.has(name)) {
119
+ this._groups.set(name, new Set())
120
+ }
121
+ for (const id of ids) {
122
+ this._groups.get(name).add(id)
123
+ // 同步更新条目的 group 字段(通过公共方法,避免直接访问私有属性)
124
+ const entry = this._entries.get(id)
125
+ if (entry && !entry.group) {
126
+ entry.setGroup(name)
127
+ }
128
+ }
129
+ }
130
+
131
+ /**
132
+ * 添加资源到分组
133
+ * @param {string} name - 分组名称
134
+ * @param {string} id - 资源 ID
135
+ */
136
+ addToGroup(name, id) {
137
+ if (!this._groups.has(name)) {
138
+ this._groups.set(name, new Set())
139
+ }
140
+ this._groups.get(name).add(id)
141
+ }
142
+
143
+ /**
144
+ * 从分组移除资源
145
+ * @param {string} name - 分组名称
146
+ * @param {string} id - 资源 ID
147
+ */
148
+ removeFromGroup(name, id) {
149
+ if (this._groups.has(name)) {
150
+ this._groups.get(name).delete(id)
151
+ if (this._groups.get(name).size === 0) {
152
+ this._groups.delete(name)
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * 获取分组内所有资源 ID
159
+ * @param {string} name - 分组名称
160
+ * @returns {string[]}
161
+ */
162
+ getGroupIds(name) {
163
+ const group = this._groups.get(name)
164
+ return group ? [...group] : []
165
+ }
166
+
167
+ /**
168
+ * 获取分组内所有资源条目
169
+ * @param {string} name - 分组名称
170
+ * @returns {import('../core/ResourceEntry.js').ResourceEntry[]}
171
+ */
172
+ getGroupEntries(name) {
173
+ const ids = this.getGroupIds(name)
174
+ return ids.map((id) => this._entries.get(id)).filter(Boolean)
175
+ }
176
+
177
+ /**
178
+ * 是否存在分组
179
+ * @param {string} name - 分组名称
180
+ * @returns {boolean}
181
+ */
182
+ hasGroup(name) {
183
+ return this._groups.has(name)
184
+ }
185
+
186
+ // --------------------------------------------------------------------------
187
+ // 标签操作
188
+ // --------------------------------------------------------------------------
189
+
190
+ /**
191
+ * 获取标签下所有资源 ID
192
+ * @param {string} tag - 标签名
193
+ * @returns {string[]}
194
+ */
195
+ getTagIds(tag) {
196
+ const tagSet = this._tags.get(tag)
197
+ return tagSet ? [...tagSet] : []
198
+ }
199
+
200
+ // --------------------------------------------------------------------------
201
+ // 查询方法
202
+ // --------------------------------------------------------------------------
203
+
204
+ /**
205
+ * 获取所有资源 ID
206
+ * @returns {string[]}
207
+ */
208
+ getAllIds() {
209
+ return [...this._entries.keys()]
210
+ }
211
+
212
+ /**
213
+ * 获取所有资源条目
214
+ * @returns {import('../core/ResourceEntry.js').ResourceEntry[]}
215
+ */
216
+ getAllEntries() {
217
+ return [...this._entries.values()]
218
+ }
219
+
220
+ /**
221
+ * 获取所有分组名称
222
+ * @returns {string[]}
223
+ */
224
+ getGroupNames() {
225
+ return [...this._groups.keys()]
226
+ }
227
+
228
+ /**
229
+ * 获取统计信息
230
+ * @returns {Object}
231
+ */
232
+ getStats() {
233
+ let loadedCount = 0
234
+ let loadingCount = 0
235
+ let errorCount = 0
236
+ let registeredCount = 0
237
+
238
+ for (const entry of this._entries.values()) {
239
+ // 使用 ResourceState 枚举值比较,避免硬编码字符串
240
+ switch (entry.state) {
241
+ case ResourceState.LOADED: loadedCount++; break
242
+ case ResourceState.LOADING: loadingCount++; break
243
+ case ResourceState.ERROR: errorCount++; break
244
+ case ResourceState.REGISTERED: registeredCount++; break
245
+ }
246
+ }
247
+
248
+ return {
249
+ total: this._entries.size,
250
+ loaded: loadedCount,
251
+ loading: loadingCount,
252
+ error: errorCount,
253
+ registered: registeredCount,
254
+ groups: this._groups.size,
255
+ tags: this._tags.size
256
+ }
257
+ }
258
+
259
+ // --------------------------------------------------------------------------
260
+ // 清理
261
+ // --------------------------------------------------------------------------
262
+
263
+ /**
264
+ * 清空所有缓存
265
+ */
266
+ clear() {
267
+ this._entries.clear()
268
+ this._groups.clear()
269
+ this._tags.clear()
270
+ }
271
+
272
+ /**
273
+ * 获取资源总数
274
+ * @type {number}
275
+ */
276
+ get size() {
277
+ return this._entries.size
278
+ }
279
+ }
280
+
281
+ export { ResourceCache }