@esengine/timer 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ESEngine Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,459 @@
1
+ import * as _esengine_ecs_framework from '@esengine/ecs-framework';
2
+ import { BlueprintNodeTemplate, INodeExecutor, BlueprintNode, ExecutionResult } from '@esengine/blueprint';
3
+
4
+ /**
5
+ * @zh 定时器服务接口
6
+ * @en Timer Service Interfaces
7
+ *
8
+ * @zh 提供定时器和冷却系统的核心接口
9
+ * @en Provides core interfaces for timer and cooldown systems
10
+ */
11
+ /**
12
+ * @zh 定时器句柄,用于取消定时器
13
+ * @en Timer handle for cancelling timers
14
+ */
15
+ interface TimerHandle {
16
+ /**
17
+ * @zh 定时器 ID
18
+ * @en Timer ID
19
+ */
20
+ readonly id: string;
21
+ /**
22
+ * @zh 是否有效(未被取消)
23
+ * @en Whether the timer is still valid (not cancelled)
24
+ */
25
+ readonly isValid: boolean;
26
+ /**
27
+ * @zh 取消定时器
28
+ * @en Cancel the timer
29
+ */
30
+ cancel(): void;
31
+ }
32
+ /**
33
+ * @zh 定时器信息
34
+ * @en Timer information
35
+ */
36
+ interface TimerInfo {
37
+ /**
38
+ * @zh 定时器 ID
39
+ * @en Timer ID
40
+ */
41
+ readonly id: string;
42
+ /**
43
+ * @zh 剩余时间(毫秒)
44
+ * @en Remaining time in milliseconds
45
+ */
46
+ readonly remaining: number;
47
+ /**
48
+ * @zh 是否重复执行
49
+ * @en Whether the timer repeats
50
+ */
51
+ readonly repeating: boolean;
52
+ /**
53
+ * @zh 间隔时间(毫秒,仅重复定时器)
54
+ * @en Interval in milliseconds (only for repeating timers)
55
+ */
56
+ readonly interval?: number;
57
+ }
58
+ /**
59
+ * @zh 冷却信息
60
+ * @en Cooldown information
61
+ */
62
+ interface CooldownInfo {
63
+ /**
64
+ * @zh 冷却 ID
65
+ * @en Cooldown ID
66
+ */
67
+ readonly id: string;
68
+ /**
69
+ * @zh 总持续时间(毫秒)
70
+ * @en Total duration in milliseconds
71
+ */
72
+ readonly duration: number;
73
+ /**
74
+ * @zh 剩余时间(毫秒)
75
+ * @en Remaining time in milliseconds
76
+ */
77
+ readonly remaining: number;
78
+ /**
79
+ * @zh 进度(0-1,0 表示刚开始,1 表示结束)
80
+ * @en Progress from 0 to 1 (0 = just started, 1 = finished)
81
+ */
82
+ readonly progress: number;
83
+ /**
84
+ * @zh 是否已就绪(冷却完成)
85
+ * @en Whether the cooldown is ready (finished)
86
+ */
87
+ readonly isReady: boolean;
88
+ }
89
+ /**
90
+ * @zh 定时器回调函数
91
+ * @en Timer callback function
92
+ */
93
+ type TimerCallback = () => void;
94
+ /**
95
+ * @zh 带时间参数的定时器回调
96
+ * @en Timer callback with time parameter
97
+ */
98
+ type TimerCallbackWithTime = (deltaTime: number) => void;
99
+ /**
100
+ * @zh 定时器服务接口
101
+ * @en Timer service interface
102
+ *
103
+ * @zh 提供定时器调度和冷却管理功能
104
+ * @en Provides timer scheduling and cooldown management
105
+ */
106
+ interface ITimerService {
107
+ /**
108
+ * @zh 调度一次性定时器
109
+ * @en Schedule a one-time timer
110
+ *
111
+ * @param id - @zh 定时器标识 @en Timer identifier
112
+ * @param delay - @zh 延迟时间(毫秒)@en Delay in milliseconds
113
+ * @param callback - @zh 回调函数 @en Callback function
114
+ * @returns @zh 定时器句柄 @en Timer handle
115
+ */
116
+ schedule(id: string, delay: number, callback: TimerCallback): TimerHandle;
117
+ /**
118
+ * @zh 调度重复定时器
119
+ * @en Schedule a repeating timer
120
+ *
121
+ * @param id - @zh 定时器标识 @en Timer identifier
122
+ * @param interval - @zh 间隔时间(毫秒)@en Interval in milliseconds
123
+ * @param callback - @zh 回调函数 @en Callback function
124
+ * @param immediate - @zh 是否立即执行一次 @en Whether to execute immediately
125
+ * @returns @zh 定时器句柄 @en Timer handle
126
+ */
127
+ scheduleRepeating(id: string, interval: number, callback: TimerCallback, immediate?: boolean): TimerHandle;
128
+ /**
129
+ * @zh 取消定时器
130
+ * @en Cancel a timer
131
+ *
132
+ * @param handle - @zh 定时器句柄 @en Timer handle
133
+ */
134
+ cancel(handle: TimerHandle): void;
135
+ /**
136
+ * @zh 通过 ID 取消定时器
137
+ * @en Cancel timer by ID
138
+ *
139
+ * @param id - @zh 定时器标识 @en Timer identifier
140
+ */
141
+ cancelById(id: string): void;
142
+ /**
143
+ * @zh 检查定时器是否存在
144
+ * @en Check if a timer exists
145
+ *
146
+ * @param id - @zh 定时器标识 @en Timer identifier
147
+ * @returns @zh 是否存在 @en Whether the timer exists
148
+ */
149
+ hasTimer(id: string): boolean;
150
+ /**
151
+ * @zh 获取定时器信息
152
+ * @en Get timer information
153
+ *
154
+ * @param id - @zh 定时器标识 @en Timer identifier
155
+ * @returns @zh 定时器信息或 null @en Timer info or null
156
+ */
157
+ getTimerInfo(id: string): TimerInfo | null;
158
+ /**
159
+ * @zh 开始冷却
160
+ * @en Start a cooldown
161
+ *
162
+ * @param id - @zh 冷却标识 @en Cooldown identifier
163
+ * @param duration - @zh 持续时间(毫秒)@en Duration in milliseconds
164
+ */
165
+ startCooldown(id: string, duration: number): void;
166
+ /**
167
+ * @zh 检查是否在冷却中
168
+ * @en Check if on cooldown
169
+ *
170
+ * @param id - @zh 冷却标识 @en Cooldown identifier
171
+ * @returns @zh 是否在冷却中 @en Whether on cooldown
172
+ */
173
+ isOnCooldown(id: string): boolean;
174
+ /**
175
+ * @zh 检查冷却是否就绪
176
+ * @en Check if cooldown is ready
177
+ *
178
+ * @param id - @zh 冷却标识 @en Cooldown identifier
179
+ * @returns @zh 是否已就绪 @en Whether ready
180
+ */
181
+ isCooldownReady(id: string): boolean;
182
+ /**
183
+ * @zh 获取剩余冷却时间
184
+ * @en Get remaining cooldown time
185
+ *
186
+ * @param id - @zh 冷却标识 @en Cooldown identifier
187
+ * @returns @zh 剩余时间(毫秒),0 表示无冷却 @en Remaining time in ms, 0 if no cooldown
188
+ */
189
+ getCooldownRemaining(id: string): number;
190
+ /**
191
+ * @zh 获取冷却进度
192
+ * @en Get cooldown progress
193
+ *
194
+ * @param id - @zh 冷却标识 @en Cooldown identifier
195
+ * @returns @zh 进度(0-1),1 表示完成或无冷却 @en Progress 0-1, 1 if done or no cooldown
196
+ */
197
+ getCooldownProgress(id: string): number;
198
+ /**
199
+ * @zh 获取冷却信息
200
+ * @en Get cooldown information
201
+ *
202
+ * @param id - @zh 冷却标识 @en Cooldown identifier
203
+ * @returns @zh 冷却信息或 null @en Cooldown info or null
204
+ */
205
+ getCooldownInfo(id: string): CooldownInfo | null;
206
+ /**
207
+ * @zh 重置冷却
208
+ * @en Reset a cooldown
209
+ *
210
+ * @param id - @zh 冷却标识 @en Cooldown identifier
211
+ */
212
+ resetCooldown(id: string): void;
213
+ /**
214
+ * @zh 清除所有冷却
215
+ * @en Clear all cooldowns
216
+ */
217
+ clearAllCooldowns(): void;
218
+ /**
219
+ * @zh 更新定时器服务
220
+ * @en Update timer service
221
+ *
222
+ * @param deltaTime - @zh 距上次更新的时间(毫秒)@en Time since last update in ms
223
+ */
224
+ update(deltaTime: number): void;
225
+ /**
226
+ * @zh 清除所有定时器和冷却
227
+ * @en Clear all timers and cooldowns
228
+ */
229
+ clear(): void;
230
+ }
231
+
232
+ /**
233
+ * @zh 定时器服务实现
234
+ * @en Timer Service Implementation
235
+ *
236
+ * @zh 提供定时器调度和冷却管理的默认实现
237
+ * @en Provides default implementation for timer scheduling and cooldown management
238
+ */
239
+
240
+ /**
241
+ * @zh 定时器服务配置
242
+ * @en Timer service configuration
243
+ */
244
+ interface TimerServiceConfig {
245
+ /**
246
+ * @zh 最大定时器数量(0 表示无限制)
247
+ * @en Maximum number of timers (0 for unlimited)
248
+ */
249
+ maxTimers?: number;
250
+ /**
251
+ * @zh 最大冷却数量(0 表示无限制)
252
+ * @en Maximum number of cooldowns (0 for unlimited)
253
+ */
254
+ maxCooldowns?: number;
255
+ }
256
+ /**
257
+ * @zh 定时器服务实现
258
+ * @en Timer service implementation
259
+ *
260
+ * @example
261
+ * ```typescript
262
+ * const timerService = new TimerService();
263
+ *
264
+ * // 一次性定时器 | One-time timer
265
+ * const handle = timerService.schedule('myTimer', 1000, () => {
266
+ * console.log('Timer fired!');
267
+ * });
268
+ *
269
+ * // 重复定时器 | Repeating timer
270
+ * timerService.scheduleRepeating('heartbeat', 100, () => {
271
+ * console.log('Tick');
272
+ * });
273
+ *
274
+ * // 冷却系统 | Cooldown system
275
+ * timerService.startCooldown('skill_fireball', 5000);
276
+ * if (timerService.isCooldownReady('skill_fireball')) {
277
+ * // 可以使用技能 | Can use skill
278
+ * }
279
+ *
280
+ * // 每帧更新 | Update each frame
281
+ * timerService.update(deltaTime);
282
+ * ```
283
+ */
284
+ declare class TimerService implements ITimerService {
285
+ private timers;
286
+ private cooldowns;
287
+ private config;
288
+ constructor(config?: TimerServiceConfig);
289
+ schedule(id: string, delay: number, callback: TimerCallback): TimerHandle;
290
+ scheduleRepeating(id: string, interval: number, callback: TimerCallback, immediate?: boolean): TimerHandle;
291
+ cancel(handle: TimerHandle): void;
292
+ cancelById(id: string): void;
293
+ hasTimer(id: string): boolean;
294
+ getTimerInfo(id: string): TimerInfo | null;
295
+ startCooldown(id: string, duration: number): void;
296
+ isOnCooldown(id: string): boolean;
297
+ isCooldownReady(id: string): boolean;
298
+ getCooldownRemaining(id: string): number;
299
+ getCooldownProgress(id: string): number;
300
+ getCooldownInfo(id: string): CooldownInfo | null;
301
+ resetCooldown(id: string): void;
302
+ clearAllCooldowns(): void;
303
+ update(deltaTime: number): void;
304
+ private updateTimers;
305
+ private updateCooldowns;
306
+ clear(): void;
307
+ /**
308
+ * @zh 获取活跃定时器数量
309
+ * @en Get active timer count
310
+ */
311
+ get activeTimerCount(): number;
312
+ /**
313
+ * @zh 获取活跃冷却数量
314
+ * @en Get active cooldown count
315
+ */
316
+ get activeCooldownCount(): number;
317
+ /**
318
+ * @zh 获取所有活跃定时器 ID
319
+ * @en Get all active timer IDs
320
+ */
321
+ getActiveTimerIds(): string[];
322
+ /**
323
+ * @zh 获取所有活跃冷却 ID
324
+ * @en Get all active cooldown IDs
325
+ */
326
+ getActiveCooldownIds(): string[];
327
+ }
328
+ /**
329
+ * @zh 创建定时器服务
330
+ * @en Create timer service
331
+ *
332
+ * @param config - @zh 配置选项 @en Configuration options
333
+ * @returns @zh 定时器服务实例 @en Timer service instance
334
+ */
335
+ declare function createTimerService(config?: TimerServiceConfig): ITimerService;
336
+
337
+ /**
338
+ * @zh 定时器服务令牌
339
+ * @en Timer service token
340
+ *
341
+ * @zh 用于注入定时器服务
342
+ * @en Used for injecting timer service
343
+ */
344
+ declare const TimerServiceToken: _esengine_ecs_framework.ServiceToken<ITimerService>;
345
+
346
+ /**
347
+ * @zh 定时器蓝图节点
348
+ * @en Timer Blueprint Nodes
349
+ *
350
+ * @zh 提供定时器和冷却功能的蓝图节点
351
+ * @en Provides blueprint nodes for timer and cooldown functionality
352
+ */
353
+
354
+ /**
355
+ * @zh StartCooldown 节点模板
356
+ * @en StartCooldown node template
357
+ */
358
+ declare const StartCooldownTemplate: BlueprintNodeTemplate;
359
+ /**
360
+ * @zh StartCooldown 节点执行器
361
+ * @en StartCooldown node executor
362
+ */
363
+ declare class StartCooldownExecutor implements INodeExecutor {
364
+ execute(node: BlueprintNode, context: unknown): ExecutionResult;
365
+ }
366
+ /**
367
+ * @zh IsCooldownReady 节点模板
368
+ * @en IsCooldownReady node template
369
+ */
370
+ declare const IsCooldownReadyTemplate: BlueprintNodeTemplate;
371
+ /**
372
+ * @zh IsCooldownReady 节点执行器
373
+ * @en IsCooldownReady node executor
374
+ */
375
+ declare class IsCooldownReadyExecutor implements INodeExecutor {
376
+ execute(node: BlueprintNode, context: unknown): ExecutionResult;
377
+ }
378
+ /**
379
+ * @zh GetCooldownProgress 节点模板
380
+ * @en GetCooldownProgress node template
381
+ */
382
+ declare const GetCooldownProgressTemplate: BlueprintNodeTemplate;
383
+ /**
384
+ * @zh GetCooldownProgress 节点执行器
385
+ * @en GetCooldownProgress node executor
386
+ */
387
+ declare class GetCooldownProgressExecutor implements INodeExecutor {
388
+ execute(node: BlueprintNode, context: unknown): ExecutionResult;
389
+ }
390
+ /**
391
+ * @zh ResetCooldown 节点模板
392
+ * @en ResetCooldown node template
393
+ */
394
+ declare const ResetCooldownTemplate: BlueprintNodeTemplate;
395
+ /**
396
+ * @zh ResetCooldown 节点执行器
397
+ * @en ResetCooldown node executor
398
+ */
399
+ declare class ResetCooldownExecutor implements INodeExecutor {
400
+ execute(node: BlueprintNode, context: unknown): ExecutionResult;
401
+ }
402
+ /**
403
+ * @zh GetCooldownInfo 节点模板
404
+ * @en GetCooldownInfo node template
405
+ */
406
+ declare const GetCooldownInfoTemplate: BlueprintNodeTemplate;
407
+ /**
408
+ * @zh GetCooldownInfo 节点执行器
409
+ * @en GetCooldownInfo node executor
410
+ */
411
+ declare class GetCooldownInfoExecutor implements INodeExecutor {
412
+ execute(node: BlueprintNode, context: unknown): ExecutionResult;
413
+ }
414
+ /**
415
+ * @zh HasTimer 节点模板
416
+ * @en HasTimer node template
417
+ */
418
+ declare const HasTimerTemplate: BlueprintNodeTemplate;
419
+ /**
420
+ * @zh HasTimer 节点执行器
421
+ * @en HasTimer node executor
422
+ */
423
+ declare class HasTimerExecutor implements INodeExecutor {
424
+ execute(node: BlueprintNode, context: unknown): ExecutionResult;
425
+ }
426
+ /**
427
+ * @zh CancelTimer 节点模板
428
+ * @en CancelTimer node template
429
+ */
430
+ declare const CancelTimerTemplate: BlueprintNodeTemplate;
431
+ /**
432
+ * @zh CancelTimer 节点执行器
433
+ * @en CancelTimer node executor
434
+ */
435
+ declare class CancelTimerExecutor implements INodeExecutor {
436
+ execute(node: BlueprintNode, context: unknown): ExecutionResult;
437
+ }
438
+ /**
439
+ * @zh GetTimerRemaining 节点模板
440
+ * @en GetTimerRemaining node template
441
+ */
442
+ declare const GetTimerRemainingTemplate: BlueprintNodeTemplate;
443
+ /**
444
+ * @zh GetTimerRemaining 节点执行器
445
+ * @en GetTimerRemaining node executor
446
+ */
447
+ declare class GetTimerRemainingExecutor implements INodeExecutor {
448
+ execute(node: BlueprintNode, context: unknown): ExecutionResult;
449
+ }
450
+ /**
451
+ * @zh 定时器节点定义
452
+ * @en Timer node definitions
453
+ */
454
+ declare const TimerNodeDefinitions: {
455
+ template: BlueprintNodeTemplate;
456
+ executor: StartCooldownExecutor;
457
+ }[];
458
+
459
+ export { CancelTimerExecutor, CancelTimerTemplate, type CooldownInfo, GetCooldownInfoExecutor, GetCooldownInfoTemplate, GetCooldownProgressExecutor, GetCooldownProgressTemplate, GetTimerRemainingExecutor, GetTimerRemainingTemplate, HasTimerExecutor, HasTimerTemplate, type ITimerService, IsCooldownReadyExecutor, IsCooldownReadyTemplate, ResetCooldownExecutor, ResetCooldownTemplate, StartCooldownExecutor, StartCooldownTemplate, type TimerCallback, type TimerCallbackWithTime, type TimerHandle, type TimerInfo, TimerNodeDefinitions, TimerService, type TimerServiceConfig, TimerServiceToken, createTimerService };
package/dist/index.js ADDED
@@ -0,0 +1,762 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+
6
+ // src/TimerService.ts
7
+ var _a;
8
+ var TimerHandleImpl = (_a = class {
9
+ constructor(timer) {
10
+ __publicField(this, "timer");
11
+ this.timer = timer;
12
+ }
13
+ get id() {
14
+ return this.timer.id;
15
+ }
16
+ get isValid() {
17
+ return !this.timer.cancelled;
18
+ }
19
+ cancel() {
20
+ this.timer.cancelled = true;
21
+ }
22
+ }, __name(_a, "TimerHandleImpl"), _a);
23
+ var _TimerService = class _TimerService {
24
+ constructor(config = {}) {
25
+ __publicField(this, "timers", /* @__PURE__ */ new Map());
26
+ __publicField(this, "cooldowns", /* @__PURE__ */ new Map());
27
+ __publicField(this, "config");
28
+ this.config = {
29
+ maxTimers: config.maxTimers ?? 0,
30
+ maxCooldowns: config.maxCooldowns ?? 0
31
+ };
32
+ }
33
+ // =========================================================================
34
+ // 定时器 API | Timer API
35
+ // =========================================================================
36
+ schedule(id, delay, callback) {
37
+ this.cancelById(id);
38
+ if (this.config.maxTimers > 0 && this.timers.size >= this.config.maxTimers) {
39
+ throw new Error(`Maximum timer limit reached: ${this.config.maxTimers}`);
40
+ }
41
+ const timer = {
42
+ id,
43
+ callback,
44
+ remaining: Math.max(0, delay),
45
+ repeating: false,
46
+ interval: 0,
47
+ cancelled: false
48
+ };
49
+ this.timers.set(id, timer);
50
+ return new TimerHandleImpl(timer);
51
+ }
52
+ scheduleRepeating(id, interval, callback, immediate = false) {
53
+ this.cancelById(id);
54
+ if (this.config.maxTimers > 0 && this.timers.size >= this.config.maxTimers) {
55
+ throw new Error(`Maximum timer limit reached: ${this.config.maxTimers}`);
56
+ }
57
+ const safeInterval = Math.max(1, interval);
58
+ const timer = {
59
+ id,
60
+ callback,
61
+ remaining: immediate ? 0 : safeInterval,
62
+ repeating: true,
63
+ interval: safeInterval,
64
+ cancelled: false
65
+ };
66
+ this.timers.set(id, timer);
67
+ return new TimerHandleImpl(timer);
68
+ }
69
+ cancel(handle) {
70
+ handle.cancel();
71
+ this.timers.delete(handle.id);
72
+ }
73
+ cancelById(id) {
74
+ const timer = this.timers.get(id);
75
+ if (timer) {
76
+ timer.cancelled = true;
77
+ this.timers.delete(id);
78
+ }
79
+ }
80
+ hasTimer(id) {
81
+ const timer = this.timers.get(id);
82
+ return timer !== void 0 && !timer.cancelled;
83
+ }
84
+ getTimerInfo(id) {
85
+ const timer = this.timers.get(id);
86
+ if (!timer || timer.cancelled) {
87
+ return null;
88
+ }
89
+ return {
90
+ id: timer.id,
91
+ remaining: timer.remaining,
92
+ repeating: timer.repeating,
93
+ interval: timer.repeating ? timer.interval : void 0
94
+ };
95
+ }
96
+ // =========================================================================
97
+ // 冷却 API | Cooldown API
98
+ // =========================================================================
99
+ startCooldown(id, duration) {
100
+ if (this.config.maxCooldowns > 0 && !this.cooldowns.has(id)) {
101
+ if (this.cooldowns.size >= this.config.maxCooldowns) {
102
+ throw new Error(`Maximum cooldown limit reached: ${this.config.maxCooldowns}`);
103
+ }
104
+ }
105
+ const safeDuration = Math.max(0, duration);
106
+ this.cooldowns.set(id, {
107
+ id,
108
+ duration: safeDuration,
109
+ remaining: safeDuration
110
+ });
111
+ }
112
+ isOnCooldown(id) {
113
+ const cooldown = this.cooldowns.get(id);
114
+ return cooldown !== void 0 && cooldown.remaining > 0;
115
+ }
116
+ isCooldownReady(id) {
117
+ return !this.isOnCooldown(id);
118
+ }
119
+ getCooldownRemaining(id) {
120
+ const cooldown = this.cooldowns.get(id);
121
+ return cooldown ? Math.max(0, cooldown.remaining) : 0;
122
+ }
123
+ getCooldownProgress(id) {
124
+ const cooldown = this.cooldowns.get(id);
125
+ if (!cooldown || cooldown.duration <= 0) {
126
+ return 1;
127
+ }
128
+ const elapsed = cooldown.duration - cooldown.remaining;
129
+ return Math.min(1, Math.max(0, elapsed / cooldown.duration));
130
+ }
131
+ getCooldownInfo(id) {
132
+ const cooldown = this.cooldowns.get(id);
133
+ if (!cooldown) {
134
+ return null;
135
+ }
136
+ const remaining = Math.max(0, cooldown.remaining);
137
+ const progress = cooldown.duration > 0 ? Math.min(1, (cooldown.duration - remaining) / cooldown.duration) : 1;
138
+ return {
139
+ id: cooldown.id,
140
+ duration: cooldown.duration,
141
+ remaining,
142
+ progress,
143
+ isReady: remaining <= 0
144
+ };
145
+ }
146
+ resetCooldown(id) {
147
+ this.cooldowns.delete(id);
148
+ }
149
+ clearAllCooldowns() {
150
+ this.cooldowns.clear();
151
+ }
152
+ // =========================================================================
153
+ // 更新 | Update
154
+ // =========================================================================
155
+ update(deltaTime) {
156
+ if (deltaTime <= 0) {
157
+ return;
158
+ }
159
+ this.updateTimers(deltaTime);
160
+ this.updateCooldowns(deltaTime);
161
+ }
162
+ updateTimers(deltaTime) {
163
+ const toRemove = [];
164
+ for (const [id, timer] of this.timers) {
165
+ if (timer.cancelled) {
166
+ toRemove.push(id);
167
+ continue;
168
+ }
169
+ timer.remaining -= deltaTime;
170
+ if (timer.remaining <= 0) {
171
+ try {
172
+ timer.callback();
173
+ } catch (error) {
174
+ console.error(`Timer callback error [${id}]:`, error);
175
+ }
176
+ if (timer.repeating && !timer.cancelled) {
177
+ timer.remaining += timer.interval;
178
+ if (timer.remaining < 0) {
179
+ timer.remaining = timer.interval;
180
+ }
181
+ } else {
182
+ timer.cancelled = true;
183
+ toRemove.push(id);
184
+ }
185
+ }
186
+ }
187
+ for (const id of toRemove) {
188
+ this.timers.delete(id);
189
+ }
190
+ }
191
+ updateCooldowns(deltaTime) {
192
+ const toRemove = [];
193
+ for (const [id, cooldown] of this.cooldowns) {
194
+ cooldown.remaining -= deltaTime;
195
+ if (cooldown.remaining <= 0) {
196
+ toRemove.push(id);
197
+ }
198
+ }
199
+ for (const id of toRemove) {
200
+ this.cooldowns.delete(id);
201
+ }
202
+ }
203
+ clear() {
204
+ for (const timer of this.timers.values()) {
205
+ timer.cancelled = true;
206
+ }
207
+ this.timers.clear();
208
+ this.cooldowns.clear();
209
+ }
210
+ // =========================================================================
211
+ // 调试 | Debug
212
+ // =========================================================================
213
+ /**
214
+ * @zh 获取活跃定时器数量
215
+ * @en Get active timer count
216
+ */
217
+ get activeTimerCount() {
218
+ return this.timers.size;
219
+ }
220
+ /**
221
+ * @zh 获取活跃冷却数量
222
+ * @en Get active cooldown count
223
+ */
224
+ get activeCooldownCount() {
225
+ return this.cooldowns.size;
226
+ }
227
+ /**
228
+ * @zh 获取所有活跃定时器 ID
229
+ * @en Get all active timer IDs
230
+ */
231
+ getActiveTimerIds() {
232
+ return Array.from(this.timers.keys());
233
+ }
234
+ /**
235
+ * @zh 获取所有活跃冷却 ID
236
+ * @en Get all active cooldown IDs
237
+ */
238
+ getActiveCooldownIds() {
239
+ return Array.from(this.cooldowns.keys());
240
+ }
241
+ };
242
+ __name(_TimerService, "TimerService");
243
+ var TimerService = _TimerService;
244
+ function createTimerService(config) {
245
+ return new TimerService(config);
246
+ }
247
+ __name(createTimerService, "createTimerService");
248
+
249
+ // src/tokens.ts
250
+ import { createServiceToken } from "@esengine/ecs-framework";
251
+ var TimerServiceToken = createServiceToken("timerService");
252
+
253
+ // src/nodes/TimerNodes.ts
254
+ var StartCooldownTemplate = {
255
+ type: "StartCooldown",
256
+ title: "Start Cooldown",
257
+ category: "time",
258
+ description: "Start a cooldown timer / \u5F00\u59CB\u51B7\u5374\u8BA1\u65F6",
259
+ keywords: [
260
+ "timer",
261
+ "cooldown",
262
+ "start",
263
+ "delay"
264
+ ],
265
+ menuPath: [
266
+ "Timer",
267
+ "Start Cooldown"
268
+ ],
269
+ isPure: false,
270
+ inputs: [
271
+ {
272
+ name: "exec",
273
+ displayName: "",
274
+ type: "exec"
275
+ },
276
+ {
277
+ name: "id",
278
+ displayName: "Cooldown ID",
279
+ type: "string",
280
+ defaultValue: ""
281
+ },
282
+ {
283
+ name: "duration",
284
+ displayName: "Duration (ms)",
285
+ type: "float",
286
+ defaultValue: 1e3
287
+ }
288
+ ],
289
+ outputs: [
290
+ {
291
+ name: "exec",
292
+ displayName: "",
293
+ type: "exec"
294
+ }
295
+ ],
296
+ color: "#00bcd4"
297
+ };
298
+ var _StartCooldownExecutor = class _StartCooldownExecutor {
299
+ execute(node, context) {
300
+ const ctx = context;
301
+ const id = ctx.evaluateInput(node.id, "id", "");
302
+ const duration = ctx.evaluateInput(node.id, "duration", 1e3);
303
+ if (id && ctx.timerService) {
304
+ ctx.timerService.startCooldown(id, duration);
305
+ }
306
+ return {
307
+ outputs: {},
308
+ nextExec: "exec"
309
+ };
310
+ }
311
+ };
312
+ __name(_StartCooldownExecutor, "StartCooldownExecutor");
313
+ var StartCooldownExecutor = _StartCooldownExecutor;
314
+ var IsCooldownReadyTemplate = {
315
+ type: "IsCooldownReady",
316
+ title: "Is Cooldown Ready",
317
+ category: "time",
318
+ description: "Check if cooldown is ready / \u68C0\u67E5\u51B7\u5374\u662F\u5426\u5C31\u7EEA",
319
+ keywords: [
320
+ "timer",
321
+ "cooldown",
322
+ "ready",
323
+ "check"
324
+ ],
325
+ menuPath: [
326
+ "Timer",
327
+ "Is Cooldown Ready"
328
+ ],
329
+ isPure: true,
330
+ inputs: [
331
+ {
332
+ name: "id",
333
+ displayName: "Cooldown ID",
334
+ type: "string",
335
+ defaultValue: ""
336
+ }
337
+ ],
338
+ outputs: [
339
+ {
340
+ name: "isReady",
341
+ displayName: "Is Ready",
342
+ type: "bool"
343
+ },
344
+ {
345
+ name: "isOnCooldown",
346
+ displayName: "Is On Cooldown",
347
+ type: "bool"
348
+ }
349
+ ],
350
+ color: "#00bcd4"
351
+ };
352
+ var _IsCooldownReadyExecutor = class _IsCooldownReadyExecutor {
353
+ execute(node, context) {
354
+ const ctx = context;
355
+ const id = ctx.evaluateInput(node.id, "id", "");
356
+ const isReady = id ? ctx.timerService?.isCooldownReady(id) ?? true : true;
357
+ const isOnCooldown = !isReady;
358
+ return {
359
+ outputs: {
360
+ isReady,
361
+ isOnCooldown
362
+ }
363
+ };
364
+ }
365
+ };
366
+ __name(_IsCooldownReadyExecutor, "IsCooldownReadyExecutor");
367
+ var IsCooldownReadyExecutor = _IsCooldownReadyExecutor;
368
+ var GetCooldownProgressTemplate = {
369
+ type: "GetCooldownProgress",
370
+ title: "Get Cooldown Progress",
371
+ category: "time",
372
+ description: "Get cooldown progress (0-1) / \u83B7\u53D6\u51B7\u5374\u8FDB\u5EA6 (0-1)",
373
+ keywords: [
374
+ "timer",
375
+ "cooldown",
376
+ "progress",
377
+ "remaining"
378
+ ],
379
+ menuPath: [
380
+ "Timer",
381
+ "Get Cooldown Progress"
382
+ ],
383
+ isPure: true,
384
+ inputs: [
385
+ {
386
+ name: "id",
387
+ displayName: "Cooldown ID",
388
+ type: "string",
389
+ defaultValue: ""
390
+ }
391
+ ],
392
+ outputs: [
393
+ {
394
+ name: "progress",
395
+ displayName: "Progress",
396
+ type: "float"
397
+ },
398
+ {
399
+ name: "remaining",
400
+ displayName: "Remaining (ms)",
401
+ type: "float"
402
+ },
403
+ {
404
+ name: "isReady",
405
+ displayName: "Is Ready",
406
+ type: "bool"
407
+ }
408
+ ],
409
+ color: "#00bcd4"
410
+ };
411
+ var _GetCooldownProgressExecutor = class _GetCooldownProgressExecutor {
412
+ execute(node, context) {
413
+ const ctx = context;
414
+ const id = ctx.evaluateInput(node.id, "id", "");
415
+ const progress = id ? ctx.timerService?.getCooldownProgress(id) ?? 1 : 1;
416
+ const remaining = id ? ctx.timerService?.getCooldownRemaining(id) ?? 0 : 0;
417
+ const isReady = remaining <= 0;
418
+ return {
419
+ outputs: {
420
+ progress,
421
+ remaining,
422
+ isReady
423
+ }
424
+ };
425
+ }
426
+ };
427
+ __name(_GetCooldownProgressExecutor, "GetCooldownProgressExecutor");
428
+ var GetCooldownProgressExecutor = _GetCooldownProgressExecutor;
429
+ var ResetCooldownTemplate = {
430
+ type: "ResetCooldown",
431
+ title: "Reset Cooldown",
432
+ category: "time",
433
+ description: "Reset a cooldown (make it ready) / \u91CD\u7F6E\u51B7\u5374\uFF08\u4F7F\u5176\u5C31\u7EEA\uFF09",
434
+ keywords: [
435
+ "timer",
436
+ "cooldown",
437
+ "reset",
438
+ "clear"
439
+ ],
440
+ menuPath: [
441
+ "Timer",
442
+ "Reset Cooldown"
443
+ ],
444
+ isPure: false,
445
+ inputs: [
446
+ {
447
+ name: "exec",
448
+ displayName: "",
449
+ type: "exec"
450
+ },
451
+ {
452
+ name: "id",
453
+ displayName: "Cooldown ID",
454
+ type: "string",
455
+ defaultValue: ""
456
+ }
457
+ ],
458
+ outputs: [
459
+ {
460
+ name: "exec",
461
+ displayName: "",
462
+ type: "exec"
463
+ }
464
+ ],
465
+ color: "#00bcd4"
466
+ };
467
+ var _ResetCooldownExecutor = class _ResetCooldownExecutor {
468
+ execute(node, context) {
469
+ const ctx = context;
470
+ const id = ctx.evaluateInput(node.id, "id", "");
471
+ if (id && ctx.timerService) {
472
+ ctx.timerService.resetCooldown(id);
473
+ }
474
+ return {
475
+ outputs: {},
476
+ nextExec: "exec"
477
+ };
478
+ }
479
+ };
480
+ __name(_ResetCooldownExecutor, "ResetCooldownExecutor");
481
+ var ResetCooldownExecutor = _ResetCooldownExecutor;
482
+ var GetCooldownInfoTemplate = {
483
+ type: "GetCooldownInfo",
484
+ title: "Get Cooldown Info",
485
+ category: "time",
486
+ description: "Get detailed cooldown information / \u83B7\u53D6\u8BE6\u7EC6\u51B7\u5374\u4FE1\u606F",
487
+ keywords: [
488
+ "timer",
489
+ "cooldown",
490
+ "info",
491
+ "details"
492
+ ],
493
+ menuPath: [
494
+ "Timer",
495
+ "Get Cooldown Info"
496
+ ],
497
+ isPure: true,
498
+ inputs: [
499
+ {
500
+ name: "id",
501
+ displayName: "Cooldown ID",
502
+ type: "string",
503
+ defaultValue: ""
504
+ }
505
+ ],
506
+ outputs: [
507
+ {
508
+ name: "exists",
509
+ displayName: "Exists",
510
+ type: "bool"
511
+ },
512
+ {
513
+ name: "duration",
514
+ displayName: "Duration (ms)",
515
+ type: "float"
516
+ },
517
+ {
518
+ name: "remaining",
519
+ displayName: "Remaining (ms)",
520
+ type: "float"
521
+ },
522
+ {
523
+ name: "progress",
524
+ displayName: "Progress",
525
+ type: "float"
526
+ },
527
+ {
528
+ name: "isReady",
529
+ displayName: "Is Ready",
530
+ type: "bool"
531
+ }
532
+ ],
533
+ color: "#00bcd4"
534
+ };
535
+ var _GetCooldownInfoExecutor = class _GetCooldownInfoExecutor {
536
+ execute(node, context) {
537
+ const ctx = context;
538
+ const id = ctx.evaluateInput(node.id, "id", "");
539
+ const info = id ? ctx.timerService?.getCooldownInfo(id) : null;
540
+ return {
541
+ outputs: {
542
+ exists: info !== null,
543
+ duration: info?.duration ?? 0,
544
+ remaining: info?.remaining ?? 0,
545
+ progress: info?.progress ?? 1,
546
+ isReady: info?.isReady ?? true
547
+ }
548
+ };
549
+ }
550
+ };
551
+ __name(_GetCooldownInfoExecutor, "GetCooldownInfoExecutor");
552
+ var GetCooldownInfoExecutor = _GetCooldownInfoExecutor;
553
+ var HasTimerTemplate = {
554
+ type: "HasTimer",
555
+ title: "Has Timer",
556
+ category: "time",
557
+ description: "Check if a timer exists / \u68C0\u67E5\u5B9A\u65F6\u5668\u662F\u5426\u5B58\u5728",
558
+ keywords: [
559
+ "timer",
560
+ "exists",
561
+ "check",
562
+ "has"
563
+ ],
564
+ menuPath: [
565
+ "Timer",
566
+ "Has Timer"
567
+ ],
568
+ isPure: true,
569
+ inputs: [
570
+ {
571
+ name: "id",
572
+ displayName: "Timer ID",
573
+ type: "string",
574
+ defaultValue: ""
575
+ }
576
+ ],
577
+ outputs: [
578
+ {
579
+ name: "exists",
580
+ displayName: "Exists",
581
+ type: "bool"
582
+ }
583
+ ],
584
+ color: "#00bcd4"
585
+ };
586
+ var _HasTimerExecutor = class _HasTimerExecutor {
587
+ execute(node, context) {
588
+ const ctx = context;
589
+ const id = ctx.evaluateInput(node.id, "id", "");
590
+ const exists = id ? ctx.timerService?.hasTimer(id) ?? false : false;
591
+ return {
592
+ outputs: {
593
+ exists
594
+ }
595
+ };
596
+ }
597
+ };
598
+ __name(_HasTimerExecutor, "HasTimerExecutor");
599
+ var HasTimerExecutor = _HasTimerExecutor;
600
+ var CancelTimerTemplate = {
601
+ type: "CancelTimer",
602
+ title: "Cancel Timer",
603
+ category: "time",
604
+ description: "Cancel a timer by ID / \u901A\u8FC7 ID \u53D6\u6D88\u5B9A\u65F6\u5668",
605
+ keywords: [
606
+ "timer",
607
+ "cancel",
608
+ "stop",
609
+ "clear"
610
+ ],
611
+ menuPath: [
612
+ "Timer",
613
+ "Cancel Timer"
614
+ ],
615
+ isPure: false,
616
+ inputs: [
617
+ {
618
+ name: "exec",
619
+ displayName: "",
620
+ type: "exec"
621
+ },
622
+ {
623
+ name: "id",
624
+ displayName: "Timer ID",
625
+ type: "string",
626
+ defaultValue: ""
627
+ }
628
+ ],
629
+ outputs: [
630
+ {
631
+ name: "exec",
632
+ displayName: "",
633
+ type: "exec"
634
+ }
635
+ ],
636
+ color: "#00bcd4"
637
+ };
638
+ var _CancelTimerExecutor = class _CancelTimerExecutor {
639
+ execute(node, context) {
640
+ const ctx = context;
641
+ const id = ctx.evaluateInput(node.id, "id", "");
642
+ if (id && ctx.timerService) {
643
+ ctx.timerService.cancelById(id);
644
+ }
645
+ return {
646
+ outputs: {},
647
+ nextExec: "exec"
648
+ };
649
+ }
650
+ };
651
+ __name(_CancelTimerExecutor, "CancelTimerExecutor");
652
+ var CancelTimerExecutor = _CancelTimerExecutor;
653
+ var GetTimerRemainingTemplate = {
654
+ type: "GetTimerRemaining",
655
+ title: "Get Timer Remaining",
656
+ category: "time",
657
+ description: "Get remaining time for a timer / \u83B7\u53D6\u5B9A\u65F6\u5668\u5269\u4F59\u65F6\u95F4",
658
+ keywords: [
659
+ "timer",
660
+ "remaining",
661
+ "time",
662
+ "left"
663
+ ],
664
+ menuPath: [
665
+ "Timer",
666
+ "Get Timer Remaining"
667
+ ],
668
+ isPure: true,
669
+ inputs: [
670
+ {
671
+ name: "id",
672
+ displayName: "Timer ID",
673
+ type: "string",
674
+ defaultValue: ""
675
+ }
676
+ ],
677
+ outputs: [
678
+ {
679
+ name: "remaining",
680
+ displayName: "Remaining (ms)",
681
+ type: "float"
682
+ },
683
+ {
684
+ name: "exists",
685
+ displayName: "Exists",
686
+ type: "bool"
687
+ }
688
+ ],
689
+ color: "#00bcd4"
690
+ };
691
+ var _GetTimerRemainingExecutor = class _GetTimerRemainingExecutor {
692
+ execute(node, context) {
693
+ const ctx = context;
694
+ const id = ctx.evaluateInput(node.id, "id", "");
695
+ const info = id ? ctx.timerService?.getTimerInfo(id) : null;
696
+ return {
697
+ outputs: {
698
+ remaining: info?.remaining ?? 0,
699
+ exists: info !== null
700
+ }
701
+ };
702
+ }
703
+ };
704
+ __name(_GetTimerRemainingExecutor, "GetTimerRemainingExecutor");
705
+ var GetTimerRemainingExecutor = _GetTimerRemainingExecutor;
706
+ var TimerNodeDefinitions = [
707
+ {
708
+ template: StartCooldownTemplate,
709
+ executor: new StartCooldownExecutor()
710
+ },
711
+ {
712
+ template: IsCooldownReadyTemplate,
713
+ executor: new IsCooldownReadyExecutor()
714
+ },
715
+ {
716
+ template: GetCooldownProgressTemplate,
717
+ executor: new GetCooldownProgressExecutor()
718
+ },
719
+ {
720
+ template: ResetCooldownTemplate,
721
+ executor: new ResetCooldownExecutor()
722
+ },
723
+ {
724
+ template: GetCooldownInfoTemplate,
725
+ executor: new GetCooldownInfoExecutor()
726
+ },
727
+ {
728
+ template: HasTimerTemplate,
729
+ executor: new HasTimerExecutor()
730
+ },
731
+ {
732
+ template: CancelTimerTemplate,
733
+ executor: new CancelTimerExecutor()
734
+ },
735
+ {
736
+ template: GetTimerRemainingTemplate,
737
+ executor: new GetTimerRemainingExecutor()
738
+ }
739
+ ];
740
+ export {
741
+ CancelTimerExecutor,
742
+ CancelTimerTemplate,
743
+ GetCooldownInfoExecutor,
744
+ GetCooldownInfoTemplate,
745
+ GetCooldownProgressExecutor,
746
+ GetCooldownProgressTemplate,
747
+ GetTimerRemainingExecutor,
748
+ GetTimerRemainingTemplate,
749
+ HasTimerExecutor,
750
+ HasTimerTemplate,
751
+ IsCooldownReadyExecutor,
752
+ IsCooldownReadyTemplate,
753
+ ResetCooldownExecutor,
754
+ ResetCooldownTemplate,
755
+ StartCooldownExecutor,
756
+ StartCooldownTemplate,
757
+ TimerNodeDefinitions,
758
+ TimerService,
759
+ TimerServiceToken,
760
+ createTimerService
761
+ };
762
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/TimerService.ts","../src/tokens.ts","../src/nodes/TimerNodes.ts"],"sourcesContent":["/**\n * @zh 定时器服务实现\n * @en Timer Service Implementation\n *\n * @zh 提供定时器调度和冷却管理的默认实现\n * @en Provides default implementation for timer scheduling and cooldown management\n */\n\nimport type {\n ITimerService,\n TimerHandle,\n TimerInfo,\n TimerCallback,\n CooldownInfo\n} from './ITimerService';\n\n// =============================================================================\n// 内部类型 | Internal Types\n// =============================================================================\n\n/**\n * @zh 内部定时器数据\n * @en Internal timer data\n */\ninterface InternalTimer {\n id: string;\n callback: TimerCallback;\n remaining: number;\n repeating: boolean;\n interval: number;\n cancelled: boolean;\n}\n\n/**\n * @zh 内部冷却数据\n * @en Internal cooldown data\n */\ninterface InternalCooldown {\n id: string;\n duration: number;\n remaining: number;\n}\n\n// =============================================================================\n// 定时器句柄实现 | Timer Handle Implementation\n// =============================================================================\n\n/**\n * @zh 定时器句柄实现\n * @en Timer handle implementation\n */\nclass TimerHandleImpl implements TimerHandle {\n private timer: InternalTimer;\n\n constructor(timer: InternalTimer) {\n this.timer = timer;\n }\n\n get id(): string {\n return this.timer.id;\n }\n\n get isValid(): boolean {\n return !this.timer.cancelled;\n }\n\n cancel(): void {\n this.timer.cancelled = true;\n }\n}\n\n// =============================================================================\n// 定时器服务实现 | Timer Service Implementation\n// =============================================================================\n\n/**\n * @zh 定时器服务配置\n * @en Timer service configuration\n */\nexport interface TimerServiceConfig {\n /**\n * @zh 最大定时器数量(0 表示无限制)\n * @en Maximum number of timers (0 for unlimited)\n */\n maxTimers?: number;\n\n /**\n * @zh 最大冷却数量(0 表示无限制)\n * @en Maximum number of cooldowns (0 for unlimited)\n */\n maxCooldowns?: number;\n}\n\n/**\n * @zh 定时器服务实现\n * @en Timer service implementation\n *\n * @example\n * ```typescript\n * const timerService = new TimerService();\n *\n * // 一次性定时器 | One-time timer\n * const handle = timerService.schedule('myTimer', 1000, () => {\n * console.log('Timer fired!');\n * });\n *\n * // 重复定时器 | Repeating timer\n * timerService.scheduleRepeating('heartbeat', 100, () => {\n * console.log('Tick');\n * });\n *\n * // 冷却系统 | Cooldown system\n * timerService.startCooldown('skill_fireball', 5000);\n * if (timerService.isCooldownReady('skill_fireball')) {\n * // 可以使用技能 | Can use skill\n * }\n *\n * // 每帧更新 | Update each frame\n * timerService.update(deltaTime);\n * ```\n */\nexport class TimerService implements ITimerService {\n private timers: Map<string, InternalTimer> = new Map();\n private cooldowns: Map<string, InternalCooldown> = new Map();\n private config: Required<TimerServiceConfig>;\n\n constructor(config: TimerServiceConfig = {}) {\n this.config = {\n maxTimers: config.maxTimers ?? 0,\n maxCooldowns: config.maxCooldowns ?? 0\n };\n }\n\n // =========================================================================\n // 定时器 API | Timer API\n // =========================================================================\n\n schedule(id: string, delay: number, callback: TimerCallback): TimerHandle {\n this.cancelById(id);\n\n if (this.config.maxTimers > 0 && this.timers.size >= this.config.maxTimers) {\n throw new Error(`Maximum timer limit reached: ${this.config.maxTimers}`);\n }\n\n const timer: InternalTimer = {\n id,\n callback,\n remaining: Math.max(0, delay),\n repeating: false,\n interval: 0,\n cancelled: false\n };\n\n this.timers.set(id, timer);\n return new TimerHandleImpl(timer);\n }\n\n scheduleRepeating(\n id: string,\n interval: number,\n callback: TimerCallback,\n immediate = false\n ): TimerHandle {\n this.cancelById(id);\n\n if (this.config.maxTimers > 0 && this.timers.size >= this.config.maxTimers) {\n throw new Error(`Maximum timer limit reached: ${this.config.maxTimers}`);\n }\n\n const safeInterval = Math.max(1, interval);\n\n const timer: InternalTimer = {\n id,\n callback,\n remaining: immediate ? 0 : safeInterval,\n repeating: true,\n interval: safeInterval,\n cancelled: false\n };\n\n this.timers.set(id, timer);\n return new TimerHandleImpl(timer);\n }\n\n cancel(handle: TimerHandle): void {\n handle.cancel();\n this.timers.delete(handle.id);\n }\n\n cancelById(id: string): void {\n const timer = this.timers.get(id);\n if (timer) {\n timer.cancelled = true;\n this.timers.delete(id);\n }\n }\n\n hasTimer(id: string): boolean {\n const timer = this.timers.get(id);\n return timer !== undefined && !timer.cancelled;\n }\n\n getTimerInfo(id: string): TimerInfo | null {\n const timer = this.timers.get(id);\n if (!timer || timer.cancelled) {\n return null;\n }\n\n return {\n id: timer.id,\n remaining: timer.remaining,\n repeating: timer.repeating,\n interval: timer.repeating ? timer.interval : undefined\n };\n }\n\n // =========================================================================\n // 冷却 API | Cooldown API\n // =========================================================================\n\n startCooldown(id: string, duration: number): void {\n if (this.config.maxCooldowns > 0 && !this.cooldowns.has(id)) {\n if (this.cooldowns.size >= this.config.maxCooldowns) {\n throw new Error(`Maximum cooldown limit reached: ${this.config.maxCooldowns}`);\n }\n }\n\n const safeDuration = Math.max(0, duration);\n\n this.cooldowns.set(id, {\n id,\n duration: safeDuration,\n remaining: safeDuration\n });\n }\n\n isOnCooldown(id: string): boolean {\n const cooldown = this.cooldowns.get(id);\n return cooldown !== undefined && cooldown.remaining > 0;\n }\n\n isCooldownReady(id: string): boolean {\n return !this.isOnCooldown(id);\n }\n\n getCooldownRemaining(id: string): number {\n const cooldown = this.cooldowns.get(id);\n return cooldown ? Math.max(0, cooldown.remaining) : 0;\n }\n\n getCooldownProgress(id: string): number {\n const cooldown = this.cooldowns.get(id);\n if (!cooldown || cooldown.duration <= 0) {\n return 1;\n }\n\n const elapsed = cooldown.duration - cooldown.remaining;\n return Math.min(1, Math.max(0, elapsed / cooldown.duration));\n }\n\n getCooldownInfo(id: string): CooldownInfo | null {\n const cooldown = this.cooldowns.get(id);\n if (!cooldown) {\n return null;\n }\n\n const remaining = Math.max(0, cooldown.remaining);\n const progress = cooldown.duration > 0\n ? Math.min(1, (cooldown.duration - remaining) / cooldown.duration)\n : 1;\n\n return {\n id: cooldown.id,\n duration: cooldown.duration,\n remaining,\n progress,\n isReady: remaining <= 0\n };\n }\n\n resetCooldown(id: string): void {\n this.cooldowns.delete(id);\n }\n\n clearAllCooldowns(): void {\n this.cooldowns.clear();\n }\n\n // =========================================================================\n // 更新 | Update\n // =========================================================================\n\n update(deltaTime: number): void {\n if (deltaTime <= 0) {\n return;\n }\n\n this.updateTimers(deltaTime);\n this.updateCooldowns(deltaTime);\n }\n\n private updateTimers(deltaTime: number): void {\n const toRemove: string[] = [];\n\n for (const [id, timer] of this.timers) {\n if (timer.cancelled) {\n toRemove.push(id);\n continue;\n }\n\n timer.remaining -= deltaTime;\n\n if (timer.remaining <= 0) {\n try {\n timer.callback();\n } catch (error) {\n console.error(`Timer callback error [${id}]:`, error);\n }\n\n if (timer.repeating && !timer.cancelled) {\n timer.remaining += timer.interval;\n if (timer.remaining < 0) {\n timer.remaining = timer.interval;\n }\n } else {\n timer.cancelled = true;\n toRemove.push(id);\n }\n }\n }\n\n for (const id of toRemove) {\n this.timers.delete(id);\n }\n }\n\n private updateCooldowns(deltaTime: number): void {\n const toRemove: string[] = [];\n\n for (const [id, cooldown] of this.cooldowns) {\n cooldown.remaining -= deltaTime;\n\n if (cooldown.remaining <= 0) {\n toRemove.push(id);\n }\n }\n\n for (const id of toRemove) {\n this.cooldowns.delete(id);\n }\n }\n\n clear(): void {\n for (const timer of this.timers.values()) {\n timer.cancelled = true;\n }\n this.timers.clear();\n this.cooldowns.clear();\n }\n\n // =========================================================================\n // 调试 | Debug\n // =========================================================================\n\n /**\n * @zh 获取活跃定时器数量\n * @en Get active timer count\n */\n get activeTimerCount(): number {\n return this.timers.size;\n }\n\n /**\n * @zh 获取活跃冷却数量\n * @en Get active cooldown count\n */\n get activeCooldownCount(): number {\n return this.cooldowns.size;\n }\n\n /**\n * @zh 获取所有活跃定时器 ID\n * @en Get all active timer IDs\n */\n getActiveTimerIds(): string[] {\n return Array.from(this.timers.keys());\n }\n\n /**\n * @zh 获取所有活跃冷却 ID\n * @en Get all active cooldown IDs\n */\n getActiveCooldownIds(): string[] {\n return Array.from(this.cooldowns.keys());\n }\n}\n\n// =============================================================================\n// 工厂函数 | Factory Functions\n// =============================================================================\n\n/**\n * @zh 创建定时器服务\n * @en Create timer service\n *\n * @param config - @zh 配置选项 @en Configuration options\n * @returns @zh 定时器服务实例 @en Timer service instance\n */\nexport function createTimerService(config?: TimerServiceConfig): ITimerService {\n return new TimerService(config);\n}\n","/**\n * @zh 定时器服务令牌\n * @en Timer Service Tokens\n */\n\nimport { createServiceToken } from '@esengine/ecs-framework';\nimport type { ITimerService } from './ITimerService';\n\n/**\n * @zh 定时器服务令牌\n * @en Timer service token\n *\n * @zh 用于注入定时器服务\n * @en Used for injecting timer service\n */\nexport const TimerServiceToken = createServiceToken<ITimerService>('timerService');\n","/**\n * @zh 定时器蓝图节点\n * @en Timer Blueprint Nodes\n *\n * @zh 提供定时器和冷却功能的蓝图节点\n * @en Provides blueprint nodes for timer and cooldown functionality\n */\n\nimport type { BlueprintNodeTemplate, BlueprintNode, INodeExecutor, ExecutionResult } from '@esengine/blueprint';\nimport type { ITimerService } from '../ITimerService';\n\n// =============================================================================\n// 执行上下文接口 | Execution Context Interface\n// =============================================================================\n\n/**\n * @zh 定时器上下文\n * @en Timer context\n */\ninterface TimerContext {\n timerService: ITimerService;\n evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown;\n setOutputs(nodeId: string, outputs: Record<string, unknown>): void;\n}\n\n// =============================================================================\n// StartCooldown 节点 | StartCooldown Node\n// =============================================================================\n\n/**\n * @zh StartCooldown 节点模板\n * @en StartCooldown node template\n */\nexport const StartCooldownTemplate: BlueprintNodeTemplate = {\n type: 'StartCooldown',\n title: 'Start Cooldown',\n category: 'time',\n description: 'Start a cooldown timer / 开始冷却计时',\n keywords: ['timer', 'cooldown', 'start', 'delay'],\n menuPath: ['Timer', 'Start Cooldown'],\n isPure: false,\n inputs: [\n {\n name: 'exec',\n displayName: '',\n type: 'exec'\n },\n {\n name: 'id',\n displayName: 'Cooldown ID',\n type: 'string',\n defaultValue: ''\n },\n {\n name: 'duration',\n displayName: 'Duration (ms)',\n type: 'float',\n defaultValue: 1000\n }\n ],\n outputs: [\n {\n name: 'exec',\n displayName: '',\n type: 'exec'\n }\n ],\n color: '#00bcd4'\n};\n\n/**\n * @zh StartCooldown 节点执行器\n * @en StartCooldown node executor\n */\nexport class StartCooldownExecutor implements INodeExecutor {\n execute(node: BlueprintNode, context: unknown): ExecutionResult {\n const ctx = context as TimerContext;\n const id = ctx.evaluateInput(node.id, 'id', '') as string;\n const duration = ctx.evaluateInput(node.id, 'duration', 1000) as number;\n\n if (id && ctx.timerService) {\n ctx.timerService.startCooldown(id, duration);\n }\n\n return {\n outputs: {},\n nextExec: 'exec'\n };\n }\n}\n\n// =============================================================================\n// IsCooldownReady 节点 | IsCooldownReady Node\n// =============================================================================\n\n/**\n * @zh IsCooldownReady 节点模板\n * @en IsCooldownReady node template\n */\nexport const IsCooldownReadyTemplate: BlueprintNodeTemplate = {\n type: 'IsCooldownReady',\n title: 'Is Cooldown Ready',\n category: 'time',\n description: 'Check if cooldown is ready / 检查冷却是否就绪',\n keywords: ['timer', 'cooldown', 'ready', 'check'],\n menuPath: ['Timer', 'Is Cooldown Ready'],\n isPure: true,\n inputs: [\n {\n name: 'id',\n displayName: 'Cooldown ID',\n type: 'string',\n defaultValue: ''\n }\n ],\n outputs: [\n {\n name: 'isReady',\n displayName: 'Is Ready',\n type: 'bool'\n },\n {\n name: 'isOnCooldown',\n displayName: 'Is On Cooldown',\n type: 'bool'\n }\n ],\n color: '#00bcd4'\n};\n\n/**\n * @zh IsCooldownReady 节点执行器\n * @en IsCooldownReady node executor\n */\nexport class IsCooldownReadyExecutor implements INodeExecutor {\n execute(node: BlueprintNode, context: unknown): ExecutionResult {\n const ctx = context as TimerContext;\n const id = ctx.evaluateInput(node.id, 'id', '') as string;\n\n const isReady = id ? ctx.timerService?.isCooldownReady(id) ?? true : true;\n const isOnCooldown = !isReady;\n\n return {\n outputs: {\n isReady,\n isOnCooldown\n }\n };\n }\n}\n\n// =============================================================================\n// GetCooldownProgress 节点 | GetCooldownProgress Node\n// =============================================================================\n\n/**\n * @zh GetCooldownProgress 节点模板\n * @en GetCooldownProgress node template\n */\nexport const GetCooldownProgressTemplate: BlueprintNodeTemplate = {\n type: 'GetCooldownProgress',\n title: 'Get Cooldown Progress',\n category: 'time',\n description: 'Get cooldown progress (0-1) / 获取冷却进度 (0-1)',\n keywords: ['timer', 'cooldown', 'progress', 'remaining'],\n menuPath: ['Timer', 'Get Cooldown Progress'],\n isPure: true,\n inputs: [\n {\n name: 'id',\n displayName: 'Cooldown ID',\n type: 'string',\n defaultValue: ''\n }\n ],\n outputs: [\n {\n name: 'progress',\n displayName: 'Progress',\n type: 'float'\n },\n {\n name: 'remaining',\n displayName: 'Remaining (ms)',\n type: 'float'\n },\n {\n name: 'isReady',\n displayName: 'Is Ready',\n type: 'bool'\n }\n ],\n color: '#00bcd4'\n};\n\n/**\n * @zh GetCooldownProgress 节点执行器\n * @en GetCooldownProgress node executor\n */\nexport class GetCooldownProgressExecutor implements INodeExecutor {\n execute(node: BlueprintNode, context: unknown): ExecutionResult {\n const ctx = context as TimerContext;\n const id = ctx.evaluateInput(node.id, 'id', '') as string;\n\n const progress = id ? ctx.timerService?.getCooldownProgress(id) ?? 1 : 1;\n const remaining = id ? ctx.timerService?.getCooldownRemaining(id) ?? 0 : 0;\n const isReady = remaining <= 0;\n\n return {\n outputs: {\n progress,\n remaining,\n isReady\n }\n };\n }\n}\n\n// =============================================================================\n// ResetCooldown 节点 | ResetCooldown Node\n// =============================================================================\n\n/**\n * @zh ResetCooldown 节点模板\n * @en ResetCooldown node template\n */\nexport const ResetCooldownTemplate: BlueprintNodeTemplate = {\n type: 'ResetCooldown',\n title: 'Reset Cooldown',\n category: 'time',\n description: 'Reset a cooldown (make it ready) / 重置冷却(使其就绪)',\n keywords: ['timer', 'cooldown', 'reset', 'clear'],\n menuPath: ['Timer', 'Reset Cooldown'],\n isPure: false,\n inputs: [\n {\n name: 'exec',\n displayName: '',\n type: 'exec'\n },\n {\n name: 'id',\n displayName: 'Cooldown ID',\n type: 'string',\n defaultValue: ''\n }\n ],\n outputs: [\n {\n name: 'exec',\n displayName: '',\n type: 'exec'\n }\n ],\n color: '#00bcd4'\n};\n\n/**\n * @zh ResetCooldown 节点执行器\n * @en ResetCooldown node executor\n */\nexport class ResetCooldownExecutor implements INodeExecutor {\n execute(node: BlueprintNode, context: unknown): ExecutionResult {\n const ctx = context as TimerContext;\n const id = ctx.evaluateInput(node.id, 'id', '') as string;\n\n if (id && ctx.timerService) {\n ctx.timerService.resetCooldown(id);\n }\n\n return {\n outputs: {},\n nextExec: 'exec'\n };\n }\n}\n\n// =============================================================================\n// GetCooldownInfo 节点 | GetCooldownInfo Node\n// =============================================================================\n\n/**\n * @zh GetCooldownInfo 节点模板\n * @en GetCooldownInfo node template\n */\nexport const GetCooldownInfoTemplate: BlueprintNodeTemplate = {\n type: 'GetCooldownInfo',\n title: 'Get Cooldown Info',\n category: 'time',\n description: 'Get detailed cooldown information / 获取详细冷却信息',\n keywords: ['timer', 'cooldown', 'info', 'details'],\n menuPath: ['Timer', 'Get Cooldown Info'],\n isPure: true,\n inputs: [\n {\n name: 'id',\n displayName: 'Cooldown ID',\n type: 'string',\n defaultValue: ''\n }\n ],\n outputs: [\n {\n name: 'exists',\n displayName: 'Exists',\n type: 'bool'\n },\n {\n name: 'duration',\n displayName: 'Duration (ms)',\n type: 'float'\n },\n {\n name: 'remaining',\n displayName: 'Remaining (ms)',\n type: 'float'\n },\n {\n name: 'progress',\n displayName: 'Progress',\n type: 'float'\n },\n {\n name: 'isReady',\n displayName: 'Is Ready',\n type: 'bool'\n }\n ],\n color: '#00bcd4'\n};\n\n/**\n * @zh GetCooldownInfo 节点执行器\n * @en GetCooldownInfo node executor\n */\nexport class GetCooldownInfoExecutor implements INodeExecutor {\n execute(node: BlueprintNode, context: unknown): ExecutionResult {\n const ctx = context as TimerContext;\n const id = ctx.evaluateInput(node.id, 'id', '') as string;\n\n const info = id ? ctx.timerService?.getCooldownInfo(id) : null;\n\n return {\n outputs: {\n exists: info !== null,\n duration: info?.duration ?? 0,\n remaining: info?.remaining ?? 0,\n progress: info?.progress ?? 1,\n isReady: info?.isReady ?? true\n }\n };\n }\n}\n\n// =============================================================================\n// HasTimer 节点 | HasTimer Node\n// =============================================================================\n\n/**\n * @zh HasTimer 节点模板\n * @en HasTimer node template\n */\nexport const HasTimerTemplate: BlueprintNodeTemplate = {\n type: 'HasTimer',\n title: 'Has Timer',\n category: 'time',\n description: 'Check if a timer exists / 检查定时器是否存在',\n keywords: ['timer', 'exists', 'check', 'has'],\n menuPath: ['Timer', 'Has Timer'],\n isPure: true,\n inputs: [\n {\n name: 'id',\n displayName: 'Timer ID',\n type: 'string',\n defaultValue: ''\n }\n ],\n outputs: [\n {\n name: 'exists',\n displayName: 'Exists',\n type: 'bool'\n }\n ],\n color: '#00bcd4'\n};\n\n/**\n * @zh HasTimer 节点执行器\n * @en HasTimer node executor\n */\nexport class HasTimerExecutor implements INodeExecutor {\n execute(node: BlueprintNode, context: unknown): ExecutionResult {\n const ctx = context as TimerContext;\n const id = ctx.evaluateInput(node.id, 'id', '') as string;\n\n const exists = id ? ctx.timerService?.hasTimer(id) ?? false : false;\n\n return {\n outputs: {\n exists\n }\n };\n }\n}\n\n// =============================================================================\n// CancelTimer 节点 | CancelTimer Node\n// =============================================================================\n\n/**\n * @zh CancelTimer 节点模板\n * @en CancelTimer node template\n */\nexport const CancelTimerTemplate: BlueprintNodeTemplate = {\n type: 'CancelTimer',\n title: 'Cancel Timer',\n category: 'time',\n description: 'Cancel a timer by ID / 通过 ID 取消定时器',\n keywords: ['timer', 'cancel', 'stop', 'clear'],\n menuPath: ['Timer', 'Cancel Timer'],\n isPure: false,\n inputs: [\n {\n name: 'exec',\n displayName: '',\n type: 'exec'\n },\n {\n name: 'id',\n displayName: 'Timer ID',\n type: 'string',\n defaultValue: ''\n }\n ],\n outputs: [\n {\n name: 'exec',\n displayName: '',\n type: 'exec'\n }\n ],\n color: '#00bcd4'\n};\n\n/**\n * @zh CancelTimer 节点执行器\n * @en CancelTimer node executor\n */\nexport class CancelTimerExecutor implements INodeExecutor {\n execute(node: BlueprintNode, context: unknown): ExecutionResult {\n const ctx = context as TimerContext;\n const id = ctx.evaluateInput(node.id, 'id', '') as string;\n\n if (id && ctx.timerService) {\n ctx.timerService.cancelById(id);\n }\n\n return {\n outputs: {},\n nextExec: 'exec'\n };\n }\n}\n\n// =============================================================================\n// GetTimerRemaining 节点 | GetTimerRemaining Node\n// =============================================================================\n\n/**\n * @zh GetTimerRemaining 节点模板\n * @en GetTimerRemaining node template\n */\nexport const GetTimerRemainingTemplate: BlueprintNodeTemplate = {\n type: 'GetTimerRemaining',\n title: 'Get Timer Remaining',\n category: 'time',\n description: 'Get remaining time for a timer / 获取定时器剩余时间',\n keywords: ['timer', 'remaining', 'time', 'left'],\n menuPath: ['Timer', 'Get Timer Remaining'],\n isPure: true,\n inputs: [\n {\n name: 'id',\n displayName: 'Timer ID',\n type: 'string',\n defaultValue: ''\n }\n ],\n outputs: [\n {\n name: 'remaining',\n displayName: 'Remaining (ms)',\n type: 'float'\n },\n {\n name: 'exists',\n displayName: 'Exists',\n type: 'bool'\n }\n ],\n color: '#00bcd4'\n};\n\n/**\n * @zh GetTimerRemaining 节点执行器\n * @en GetTimerRemaining node executor\n */\nexport class GetTimerRemainingExecutor implements INodeExecutor {\n execute(node: BlueprintNode, context: unknown): ExecutionResult {\n const ctx = context as TimerContext;\n const id = ctx.evaluateInput(node.id, 'id', '') as string;\n\n const info = id ? ctx.timerService?.getTimerInfo(id) : null;\n\n return {\n outputs: {\n remaining: info?.remaining ?? 0,\n exists: info !== null\n }\n };\n }\n}\n\n// =============================================================================\n// 节点定义集合 | Node Definition Collection\n// =============================================================================\n\n/**\n * @zh 定时器节点定义\n * @en Timer node definitions\n */\nexport const TimerNodeDefinitions = [\n { template: StartCooldownTemplate, executor: new StartCooldownExecutor() },\n { template: IsCooldownReadyTemplate, executor: new IsCooldownReadyExecutor() },\n { template: GetCooldownProgressTemplate, executor: new GetCooldownProgressExecutor() },\n { template: ResetCooldownTemplate, executor: new ResetCooldownExecutor() },\n { template: GetCooldownInfoTemplate, executor: new GetCooldownInfoExecutor() },\n { template: HasTimerTemplate, executor: new HasTimerExecutor() },\n { template: CancelTimerTemplate, executor: new CancelTimerExecutor() },\n { template: GetTimerRemainingTemplate, executor: new GetTimerRemainingExecutor() }\n];\n"],"mappings":"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","names":["TimerHandleImpl","timer","id","isValid","cancelled","cancel","TimerService","config","timers","Map","cooldowns","maxTimers","maxCooldowns","schedule","delay","callback","cancelById","size","Error","remaining","Math","max","repeating","interval","set","scheduleRepeating","immediate","safeInterval","handle","delete","get","hasTimer","undefined","getTimerInfo","startCooldown","duration","has","safeDuration","isOnCooldown","cooldown","isCooldownReady","getCooldownRemaining","getCooldownProgress","elapsed","min","getCooldownInfo","progress","isReady","resetCooldown","clearAllCooldowns","clear","update","deltaTime","updateTimers","updateCooldowns","toRemove","push","error","console","values","activeTimerCount","activeCooldownCount","getActiveTimerIds","Array","from","keys","getActiveCooldownIds","createTimerService","createServiceToken","TimerServiceToken","StartCooldownTemplate","type","title","category","description","keywords","menuPath","isPure","inputs","name","displayName","defaultValue","outputs","color","StartCooldownExecutor","execute","node","context","ctx","id","evaluateInput","duration","timerService","startCooldown","nextExec","IsCooldownReadyTemplate","IsCooldownReadyExecutor","isReady","isCooldownReady","isOnCooldown","GetCooldownProgressTemplate","GetCooldownProgressExecutor","progress","getCooldownProgress","remaining","getCooldownRemaining","ResetCooldownTemplate","ResetCooldownExecutor","resetCooldown","GetCooldownInfoTemplate","GetCooldownInfoExecutor","info","getCooldownInfo","exists","HasTimerTemplate","HasTimerExecutor","hasTimer","CancelTimerTemplate","CancelTimerExecutor","cancelById","GetTimerRemainingTemplate","GetTimerRemainingExecutor","getTimerInfo","TimerNodeDefinitions","template","executor"]}
package/module.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "timer",
3
+ "name": "@esengine/timer",
4
+ "globalKey": "timer",
5
+ "displayName": "Timer & Cooldown",
6
+ "description": "定时器和冷却系统 | Timer and cooldown system",
7
+ "version": "1.0.0",
8
+ "category": "Other",
9
+ "icon": "Timer",
10
+ "tags": ["timer", "cooldown", "delay", "schedule"],
11
+ "isCore": false,
12
+ "defaultEnabled": true,
13
+ "isEngineModule": true,
14
+ "canContainContent": false,
15
+ "platforms": ["web", "desktop"],
16
+ "dependencies": ["core"],
17
+ "exports": {
18
+ "components": [],
19
+ "systems": []
20
+ },
21
+ "outputPath": "dist/index.js",
22
+ "pluginExport": "TimerPlugin"
23
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@esengine/timer",
3
+ "version": "1.0.0",
4
+ "description": "Timer and cooldown system for ECS Framework / ECS 框架的定时器和冷却系统",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "module.json"
18
+ ],
19
+ "dependencies": {
20
+ "tslib": "^2.8.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^20.19.17",
24
+ "rimraf": "^5.0.0",
25
+ "tsup": "^8.0.0",
26
+ "typescript": "^5.8.3",
27
+ "@esengine/ecs-framework": "2.4.2",
28
+ "@esengine/blueprint": "1.0.0",
29
+ "@esengine/build-config": "1.0.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "build:watch": "tsup --watch",
37
+ "type-check": "tsc --noEmit",
38
+ "clean": "rimraf dist"
39
+ }
40
+ }