@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,374 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Nimbus2D - MemoryChecker 内存泄漏检测工具
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// 职责:
|
|
5
|
+
// 开发/调试模式下的内存泄漏检测工具。基于 WeakRef + FinalizationRegistry
|
|
6
|
+
// 实现无侵入追踪,帮助开发者发现对象未释放、对象池使用率异常等问题。
|
|
7
|
+
//
|
|
8
|
+
// 设计决策:
|
|
9
|
+
// - 不继承 Subsystem:MemoryChecker 是工具类,不参与 Lifecycle 调度
|
|
10
|
+
// - WeakRef + FinalizationRegistry:无侵入追踪,不影响对象生命周期
|
|
11
|
+
// - unregisterToken 使用独立空对象:避免以 obj 自身作为 token 导致
|
|
12
|
+
// FinalizationRegistry 持有 obj 的强引用,阻止 GC 回收
|
|
13
|
+
// - 开发模式自动挂钩 PoolManager/AssetManager,生产模式不挂钩
|
|
14
|
+
// - 自动检测通过 setInterval 定期执行,广播 memory:leak:suspected 事件
|
|
15
|
+
// - destroy() 时清理所有定时器和引用
|
|
16
|
+
//
|
|
17
|
+
// 文件位置:
|
|
18
|
+
// src/utils/MemoryChecker.js
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
class MemoryChecker {
|
|
22
|
+
// --------------------------------------------------------------------------
|
|
23
|
+
// 构造函数
|
|
24
|
+
// --------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 构造函数
|
|
28
|
+
* @param {import('../core/EventBus/EventBus.js').EventBus} eventBus - 全局事件总线
|
|
29
|
+
* @param {import('../core/ConfigSystem/ConfigSystem.js').ConfigSystem} config - 配置系统
|
|
30
|
+
* @param {import('../core/Logger/Logger.js').Logger} logger - 日志系统
|
|
31
|
+
*/
|
|
32
|
+
constructor(eventBus, config, logger) {
|
|
33
|
+
/** @type {import('../core/EventBus/EventBus.js').EventBus} 全局事件总线 */
|
|
34
|
+
this._eventBus = eventBus
|
|
35
|
+
|
|
36
|
+
/** @type {import('../core/ConfigSystem/ConfigSystem.js').ConfigSystem} 配置系统 */
|
|
37
|
+
this._config = config
|
|
38
|
+
|
|
39
|
+
/** @type {import('../core/Logger/Logger.js').Logger} 日志系统 */
|
|
40
|
+
this._logger = logger
|
|
41
|
+
|
|
42
|
+
/** @type {boolean} 是否已销毁 */
|
|
43
|
+
this._isDestroyed = false
|
|
44
|
+
|
|
45
|
+
// --- 手动追踪 ---
|
|
46
|
+
/** @type {Map<string, { total: number, alive: number, collected: number, refs: Map<WeakRef, { timestamp: number, unregisterToken: object }> }>} 追踪记录(label → 统计条目) */
|
|
47
|
+
this._tracked = new Map()
|
|
48
|
+
|
|
49
|
+
/** @type {FinalizationRegistry<string>} GC 回调注册表(回调参数为 label) */
|
|
50
|
+
this._finalizationRegistry = new FinalizationRegistry((label) => {
|
|
51
|
+
this._onObjectCollected(label)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// --- 自动挂钩 ---
|
|
55
|
+
/** @type {import('../core/PoolManager/PoolManager.js').PoolManager|null} 挂钩的 PoolManager */
|
|
56
|
+
this._poolManagerHooked = null
|
|
57
|
+
|
|
58
|
+
/** @type {import('../core/AssetManager/AssetManager.js').AssetManager|null} 挂钩的 AssetManager */
|
|
59
|
+
this._assetManagerHooked = null
|
|
60
|
+
|
|
61
|
+
/** @type {ReturnType<typeof setInterval>|null} 自动检测定时器 ID */
|
|
62
|
+
this._autoCheckTimer = null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// --------------------------------------------------------------------------
|
|
66
|
+
// 手动追踪
|
|
67
|
+
// --------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 手动追踪对象
|
|
71
|
+
* 使用 WeakRef 持有对象引用,不阻止对象被 GC 回收。
|
|
72
|
+
* 当对象被 GC 回收时,FinalizationRegistry 通知 _onObjectCollected。
|
|
73
|
+
*
|
|
74
|
+
* @param {Object} obj - 要追踪的对象(必须是对象类型)
|
|
75
|
+
* @param {string} label - 追踪标签(用于分组统计,如 'sprite'、'collider')
|
|
76
|
+
* @returns {Function} - 取消追踪函数(调用后移除追踪记录)
|
|
77
|
+
*/
|
|
78
|
+
track(obj, label) {
|
|
79
|
+
// 已销毁时忽略
|
|
80
|
+
if (this._isDestroyed) return () => {}
|
|
81
|
+
|
|
82
|
+
// 参数校验:只能追踪对象类型
|
|
83
|
+
if (!obj || typeof obj !== 'object') {
|
|
84
|
+
this._logger.warn('MemoryChecker.track', '只能追踪对象类型')
|
|
85
|
+
return () => {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 参数校验:标签必须是非空字符串
|
|
89
|
+
if (!label || typeof label !== 'string') {
|
|
90
|
+
this._logger.warn('MemoryChecker.track', '标签必须是非空字符串')
|
|
91
|
+
return () => {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 获取或创建追踪条目
|
|
95
|
+
if (!this._tracked.has(label)) {
|
|
96
|
+
this._tracked.set(label, { total: 0, alive: 0, collected: 0, refs: new Map() })
|
|
97
|
+
}
|
|
98
|
+
const entry = this._tracked.get(label)
|
|
99
|
+
|
|
100
|
+
// 创建 WeakRef 引用(不阻止对象被 GC 回收)
|
|
101
|
+
const weakRef = new WeakRef(obj)
|
|
102
|
+
const timestamp = Date.now()
|
|
103
|
+
|
|
104
|
+
// 使用独立的 unregister token 对象,避免对目标对象产生强引用
|
|
105
|
+
// 注意:若以 obj 作为 unregister token,FinalizationRegistry 会持有 obj 的强引用,
|
|
106
|
+
// 导致 obj 永远无法被 GC 回收,完全破坏 WeakRef 追踪机制
|
|
107
|
+
const unregisterToken = {}
|
|
108
|
+
|
|
109
|
+
entry.refs.set(weakRef, { timestamp, unregisterToken })
|
|
110
|
+
entry.total++
|
|
111
|
+
entry.alive++
|
|
112
|
+
|
|
113
|
+
// 注册到 FinalizationRegistry(unregisterToken 为独立对象,不阻止 obj 被 GC)
|
|
114
|
+
this._finalizationRegistry.register(obj, label, unregisterToken)
|
|
115
|
+
|
|
116
|
+
// 返回取消追踪函数
|
|
117
|
+
return () => {
|
|
118
|
+
if (this._isDestroyed) return
|
|
119
|
+
if (!this._tracked.has(label)) return
|
|
120
|
+
|
|
121
|
+
const currentEntry = this._tracked.get(label)
|
|
122
|
+
if (currentEntry.refs.has(weakRef)) {
|
|
123
|
+
currentEntry.refs.delete(weakRef)
|
|
124
|
+
currentEntry.total--
|
|
125
|
+
|
|
126
|
+
// 仅在对象仍然存活时递减 alive(若已被 GC 回收,_onObjectCollected 已递减)
|
|
127
|
+
if (weakRef.deref() !== undefined) {
|
|
128
|
+
currentEntry.alive--
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 从 FinalizationRegistry 注销(避免回调对已取消追踪的对象触发)
|
|
132
|
+
this._finalizationRegistry.unregister(unregisterToken)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// --------------------------------------------------------------------------
|
|
138
|
+
// GC 回调
|
|
139
|
+
// --------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 对象被 GC 回收时的回调(由 FinalizationRegistry 触发)
|
|
143
|
+
* @param {string} label - 追踪标签
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
_onObjectCollected(label) {
|
|
147
|
+
// 已销毁时忽略(destroy() 会清理 _tracked,之后 FinalizationRegistry 回调安全忽略)
|
|
148
|
+
if (this._isDestroyed) return
|
|
149
|
+
const entry = this._tracked.get(label)
|
|
150
|
+
if (entry) {
|
|
151
|
+
entry.alive--
|
|
152
|
+
entry.collected++
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// --------------------------------------------------------------------------
|
|
157
|
+
// 报告
|
|
158
|
+
// --------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 获取内存追踪报告
|
|
162
|
+
* 包含手动追踪统计、PoolManager 统计(若已挂钩)、AssetManager 统计(若已挂钩)
|
|
163
|
+
*
|
|
164
|
+
* @returns {{
|
|
165
|
+
* timestamp: number,
|
|
166
|
+
* manual: { total: number, alive: number, collected: number, byLabel: Object },
|
|
167
|
+
* poolManager: Object|null,
|
|
168
|
+
* assetManager: Object|null
|
|
169
|
+
* }}
|
|
170
|
+
*/
|
|
171
|
+
getReport() {
|
|
172
|
+
const report = {
|
|
173
|
+
timestamp: Date.now(),
|
|
174
|
+
manual: { total: 0, alive: 0, collected: 0, byLabel: {} },
|
|
175
|
+
poolManager: null,
|
|
176
|
+
assetManager: null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 手动追踪统计
|
|
180
|
+
for (const [label, entry] of this._tracked) {
|
|
181
|
+
// 清理已失效的 WeakRef(deref() 返回 undefined 表示对象已被 GC 回收)
|
|
182
|
+
for (const weakRef of entry.refs.keys()) {
|
|
183
|
+
if (weakRef.deref() === undefined) {
|
|
184
|
+
// 移除失效的 WeakRef 引用,但不调整 alive/collected 计数
|
|
185
|
+
// (alive 已由 _onObjectCollected 递减,collected 已递增)
|
|
186
|
+
entry.refs.delete(weakRef)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
report.manual.total += entry.total
|
|
191
|
+
report.manual.alive += entry.alive
|
|
192
|
+
report.manual.collected += entry.collected
|
|
193
|
+
report.manual.byLabel[label] = {
|
|
194
|
+
total: entry.total,
|
|
195
|
+
alive: entry.alive,
|
|
196
|
+
collected: entry.collected
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// PoolManager 统计
|
|
201
|
+
if (this._poolManagerHooked && !this._poolManagerHooked.isDestroyed) {
|
|
202
|
+
const poolStats = this._poolManagerHooked.getStats()
|
|
203
|
+
report.poolManager = { pools: poolStats.pools }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// AssetManager 统计
|
|
207
|
+
if (this._assetManagerHooked && !this._assetManagerHooked.isDestroyed) {
|
|
208
|
+
report.assetManager = this._getAssetManagerStats()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return report
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// --------------------------------------------------------------------------
|
|
215
|
+
// 自动检测
|
|
216
|
+
// --------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 启用自动检测
|
|
220
|
+
* 定期检查追踪统计,发现潜在泄漏时通过 Logger 警告和 EventBus 事件通知
|
|
221
|
+
*
|
|
222
|
+
* @param {number} [interval=5000] - 检测间隔(毫秒)
|
|
223
|
+
* @param {Object} [options={}] - 检测选项
|
|
224
|
+
* @param {number} [options.leakThreshold=100] - 泄漏疑似阈值(存活对象数超过此值且无任何回收时报告)
|
|
225
|
+
*/
|
|
226
|
+
enableAutoCheck(interval = 5000, options = {}) {
|
|
227
|
+
// 已销毁时忽略
|
|
228
|
+
if (this._isDestroyed) return
|
|
229
|
+
|
|
230
|
+
// 已有定时器时忽略(避免重复注册)
|
|
231
|
+
if (this._autoCheckTimer) return
|
|
232
|
+
|
|
233
|
+
const leakThreshold = options.leakThreshold ?? 100
|
|
234
|
+
|
|
235
|
+
this._autoCheckTimer = setInterval(() => {
|
|
236
|
+
const report = this.getReport()
|
|
237
|
+
|
|
238
|
+
// 检查手动追踪的泄漏:存活对象数 > leakThreshold 且无任何被回收的记录
|
|
239
|
+
for (const [label, stats] of Object.entries(report.manual.byLabel)) {
|
|
240
|
+
if (stats.alive > 0 && stats.collected === 0 && stats.alive > leakThreshold) {
|
|
241
|
+
this._logger.warn(
|
|
242
|
+
'MemoryChecker.autoCheck',
|
|
243
|
+
`检测到潜在泄漏: '${label}' 有 ${stats.alive} 个对象未释放`
|
|
244
|
+
)
|
|
245
|
+
this._eventBus.emit('memory:leak:suspected', {
|
|
246
|
+
label,
|
|
247
|
+
alive: stats.alive
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 检查 PoolManager 泄漏:对象池使用率超过 80%
|
|
253
|
+
if (report.poolManager) {
|
|
254
|
+
for (const [name, stats] of Object.entries(report.poolManager.pools)) {
|
|
255
|
+
if (stats.inUse > 0 && stats.inUse > stats.size * 0.8) {
|
|
256
|
+
this._logger.warn(
|
|
257
|
+
'MemoryChecker.autoCheck',
|
|
258
|
+
`对象池 '${name}' 使用率过高: ${stats.inUse}/${stats.size}`
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}, interval)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 禁用自动检测
|
|
268
|
+
* 清除定时器,停止定期检测
|
|
269
|
+
*/
|
|
270
|
+
disableAutoCheck() {
|
|
271
|
+
if (this._autoCheckTimer) {
|
|
272
|
+
clearInterval(this._autoCheckTimer)
|
|
273
|
+
this._autoCheckTimer = null
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// --------------------------------------------------------------------------
|
|
278
|
+
// 挂钩管理器
|
|
279
|
+
// --------------------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 挂钩 PoolManager
|
|
283
|
+
* 使 getReport() 包含对象池统计信息
|
|
284
|
+
* @param {import('../core/PoolManager/PoolManager.js').PoolManager} poolManager - PoolManager 实例
|
|
285
|
+
*/
|
|
286
|
+
hookPoolManager(poolManager) {
|
|
287
|
+
if (this._isDestroyed) return
|
|
288
|
+
this._poolManagerHooked = poolManager
|
|
289
|
+
this._logger.info('MemoryChecker.hookPoolManager', '已挂钩 PoolManager')
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 挂钩 AssetManager
|
|
294
|
+
* 使 getReport() 包含资源管理统计信息
|
|
295
|
+
* @param {import('../core/AssetManager/AssetManager.js').AssetManager} assetManager - AssetManager 实例
|
|
296
|
+
*/
|
|
297
|
+
hookAssetManager(assetManager) {
|
|
298
|
+
if (this._isDestroyed) return
|
|
299
|
+
this._assetManagerHooked = assetManager
|
|
300
|
+
this._logger.info('MemoryChecker.hookAssetManager', '已挂钩 AssetManager')
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// --------------------------------------------------------------------------
|
|
304
|
+
// 内部方法
|
|
305
|
+
// --------------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 获取 AssetManager 统计信息
|
|
309
|
+
* 若 AssetManager 有 getStats() 方法则调用,否则返回 null
|
|
310
|
+
* @returns {Object|null}
|
|
311
|
+
* @private
|
|
312
|
+
*/
|
|
313
|
+
_getAssetManagerStats() {
|
|
314
|
+
if (!this._assetManagerHooked) return null
|
|
315
|
+
// AssetManager 内部统计通过 getStats() 获取
|
|
316
|
+
// 若 AssetManager 无 getStats(),则返回 null
|
|
317
|
+
if (typeof this._assetManagerHooked.getStats === 'function') {
|
|
318
|
+
return this._assetManagerHooked.getStats()
|
|
319
|
+
}
|
|
320
|
+
return null
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// --------------------------------------------------------------------------
|
|
324
|
+
// 生命周期
|
|
325
|
+
// --------------------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 销毁内存检测工具
|
|
329
|
+
* 清理所有追踪记录、定时器和挂钩引用。
|
|
330
|
+
* 遍历所有追踪条目,从 FinalizationRegistry 中注销未回收的对象,
|
|
331
|
+
* 防止 GC 回收时触发已销毁的回调(虽然有 _isDestroyed 检查,但注销更彻底)。
|
|
332
|
+
*/
|
|
333
|
+
destroy() {
|
|
334
|
+
if (this._isDestroyed) return
|
|
335
|
+
|
|
336
|
+
// 停止自动检测
|
|
337
|
+
this.disableAutoCheck()
|
|
338
|
+
|
|
339
|
+
// 遍历所有追踪条目,从 FinalizationRegistry 中注销未回收的对象
|
|
340
|
+
// 防止 GC 回收时触发回调(虽然 _onObjectCollected 有 _isDestroyed 检查,但注销更彻底)
|
|
341
|
+
for (const entry of this._tracked.values()) {
|
|
342
|
+
for (const { unregisterToken } of entry.refs.values()) {
|
|
343
|
+
try {
|
|
344
|
+
this._finalizationRegistry.unregister(unregisterToken)
|
|
345
|
+
} catch (e) {
|
|
346
|
+
// 注销失败安全忽略(unregisterToken 可能已失效)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// 清理追踪记录
|
|
352
|
+
this._tracked.clear()
|
|
353
|
+
|
|
354
|
+
// 清理挂钩引用
|
|
355
|
+
this._poolManagerHooked = null
|
|
356
|
+
this._assetManagerHooked = null
|
|
357
|
+
|
|
358
|
+
// 清理核心引用
|
|
359
|
+
this._eventBus = null
|
|
360
|
+
this._config = null
|
|
361
|
+
this._logger = null
|
|
362
|
+
|
|
363
|
+
this._isDestroyed = true
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// --------------------------------------------------------------------------
|
|
367
|
+
// Getter
|
|
368
|
+
// --------------------------------------------------------------------------
|
|
369
|
+
|
|
370
|
+
/** @type {boolean} 是否已销毁 */
|
|
371
|
+
get isDestroyed() { return this._isDestroyed }
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export { MemoryChecker }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Nimbus2D - 参数校验辅助函数
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// 职责:
|
|
5
|
+
// 1. 提供类型检查辅助函数(isNonEmptyString/isPositiveNumber/isPositiveInteger)
|
|
6
|
+
// 2. 提供开发模式参数校验函数(validateDev)
|
|
7
|
+
// 3. 开发模式校验仅在 core.developmentMode 为 true 时生效,
|
|
8
|
+
// 生产模式下跳过校验以获得最佳性能
|
|
9
|
+
//
|
|
10
|
+
// 使用方式:
|
|
11
|
+
// import { validateDev, isNonEmptyString } from '../../utils/Validator.js'
|
|
12
|
+
// validateDev(this._config, isNonEmptyString(name), 'name 必须是非空字符串', PoolError)
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 检查是否为非空字符串
|
|
17
|
+
* @param {*} value - 待检查的值
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
export function isNonEmptyString(value) {
|
|
21
|
+
return typeof value === 'string' && value.length > 0
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 检查是否为正数
|
|
26
|
+
* @param {*} value - 待检查的值
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
export function isPositiveNumber(value) {
|
|
30
|
+
return typeof value === 'number' && value > 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 检查是否为正整数
|
|
35
|
+
* @param {*} value - 待检查的值
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
export function isPositiveInteger(value) {
|
|
39
|
+
return Number.isInteger(value) && value > 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 开发模式参数校验
|
|
44
|
+
* 仅在 core.developmentMode 为 true(默认)时执行校验,
|
|
45
|
+
* 生产模式下跳过校验以获得最佳性能
|
|
46
|
+
*
|
|
47
|
+
* @param {import('../core/ConfigSystem/ConfigSystem.js').ConfigSystem} config - 配置系统
|
|
48
|
+
* @param {boolean} condition - 校验条件(true 表示通过)
|
|
49
|
+
* @param {string} message - 校验失败时的错误消息
|
|
50
|
+
* @param {Function} ErrorClass - 校验失败时抛出的错误类
|
|
51
|
+
* @throws {Error} 开发模式下条件不满足时抛出指定错误
|
|
52
|
+
*/
|
|
53
|
+
export function validateDev(config, condition, message, ErrorClass) {
|
|
54
|
+
// 默认为开发模式(developmentMode 为 true 或未设置时执行校验)
|
|
55
|
+
if (config.get('core.developmentMode') !== false) {
|
|
56
|
+
if (!condition) {
|
|
57
|
+
throw new ErrorClass(message)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Nimbus2D - 工具函数
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// 通用工具函数集合
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
// 从 ConfigMerger 重导出 deepMerge,避免重复定义
|
|
8
|
+
export { deepMerge } from '../core/ConfigSystem/ConfigMerger.js'
|
|
9
|
+
|
|
10
|
+
// 参数校验辅助函数(三期新增)
|
|
11
|
+
export { isNonEmptyString, isPositiveNumber, isPositiveInteger, validateDev } from './Validator.js'
|
|
12
|
+
|
|
13
|
+
// 内存泄漏检测工具(三期新增)
|
|
14
|
+
export { MemoryChecker } from './MemoryChecker.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 生成唯一 ID
|
|
18
|
+
* @param {string} [prefix=''] - 前缀
|
|
19
|
+
* @returns {string} 唯一 ID
|
|
20
|
+
*/
|
|
21
|
+
export function generateId(prefix = '') {
|
|
22
|
+
const timestamp = Date.now().toString(36)
|
|
23
|
+
const random = Math.random().toString(36).substring(2, 8)
|
|
24
|
+
return prefix ? `${prefix}_${timestamp}_${random}` : `${timestamp}_${random}`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 防抖函数
|
|
29
|
+
* @param {Function} fn - 要防抖的函数
|
|
30
|
+
* @param {number} delay - 延迟时间(毫秒)
|
|
31
|
+
* @returns {Function} 防抖后的函数
|
|
32
|
+
*/
|
|
33
|
+
export function debounce(fn, delay) {
|
|
34
|
+
let timer = null
|
|
35
|
+
return function (...args) {
|
|
36
|
+
if (timer) clearTimeout(timer)
|
|
37
|
+
timer = setTimeout(() => {
|
|
38
|
+
fn.apply(this, args)
|
|
39
|
+
timer = null
|
|
40
|
+
}, delay)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 节流函数
|
|
46
|
+
* @param {Function} fn - 要节流的函数
|
|
47
|
+
* @param {number} interval - 间隔时间(毫秒)
|
|
48
|
+
* @returns {Function} 节流后的函数
|
|
49
|
+
*/
|
|
50
|
+
export function throttle(fn, interval) {
|
|
51
|
+
let lastTime = 0
|
|
52
|
+
return function (...args) {
|
|
53
|
+
const now = Date.now()
|
|
54
|
+
if (now - lastTime >= interval) {
|
|
55
|
+
fn.apply(this, args)
|
|
56
|
+
lastTime = now
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 安全的 JSON 解析
|
|
63
|
+
* @param {string} str - JSON 字符串
|
|
64
|
+
* @param {*} defaultValue - 解析失败时的默认值
|
|
65
|
+
* @returns {*} 解析结果或默认值
|
|
66
|
+
*/
|
|
67
|
+
export function safeJsonParse(str, defaultValue = null) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(str)
|
|
70
|
+
} catch {
|
|
71
|
+
return defaultValue
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 限制数值范围
|
|
77
|
+
* @param {number} value - 输入值
|
|
78
|
+
* @param {number} min - 最小值
|
|
79
|
+
* @param {number} max - 最大值
|
|
80
|
+
* @returns {number} 限制后的值
|
|
81
|
+
*/
|
|
82
|
+
export function clamp(value, min, max) {
|
|
83
|
+
return Math.min(Math.max(value, min), max)
|
|
84
|
+
}
|