@xiacg/exia-event 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) 2026 xiachenggang
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.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # exia-event
2
+
3
+ 轻量级事件系统,提供全局事件管理和模块间通信。
4
+
5
+ ## 简介
6
+
7
+ `exia-event` 是一个轻量级、高性能的事件系统,提供全局事件管理和模块间松耦合通信能力。支持事件的添加、移除、发送、批量管理等操作,适用于游戏各系统之间的解耦通信。
8
+
9
+ **核心特性**:
10
+
11
+ - 🚀 零依赖,轻量级实现
12
+ - 📡 全局事件管理器
13
+ - 🎯 支持按 ID、名称、目标对象移除事件
14
+ - 🔥 支持一次性事件(addOnce)
15
+ - 🎨 完整的 TypeScript 类型定义
16
+ - ⚡ 高性能事件分发机制
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ npm install @xiacg/exia-event
22
+ ```
23
+
24
+ ## 使用说明
25
+
26
+ ### 全局事件 (GlobalEvent)
27
+
28
+ 全局单例事件管理器,用于模块间通信。
29
+
30
+ **添加事件**:
31
+
32
+ - `add(name, callback, target?)` - 添加事件监听,返回事件 ID
33
+ - `addOnce(name, callback, target?)` - 添加一次性事件监听(触发后自动移除)
34
+
35
+ **发送事件**:
36
+
37
+ - `send(name, ...args)` - 发送事件给所有监听者
38
+ - `sendToTarget(name, target, ...args)` - 发送事件给指定目标
39
+
40
+ **移除事件**:
41
+
42
+ - `remove(eventId)` - 按 ID 移除事件
43
+ - `removeByName(name)` - 移除指定名称的所有事件
44
+ - `removeByTarget(target)` - 移除指定目标的所有事件
45
+ - `removeByNameAndTarget(name, target)` - 移除指定名称和目标的事件
46
+ - `clearAll()` - 清空所有事件
47
+
48
+ **参数说明**:
49
+
50
+ - `name` - 事件名称(建议使用常量管理)
51
+ - `callback` - 回调函数 `(...args: any[]) => void`
52
+ - `target` - 目标对象(用于批量移除)
53
+ - `eventId` - 事件 ID(add 方法返回)
54
+
55
+ ### 事件管理器 (EventManager)
56
+
57
+ 可创建独立的事件管理器实例,用于特定模块或系统。
58
+
59
+ **使用方式**:
60
+
61
+ - `new EventManager()` - 创建独立的事件管理器
62
+ - API 与 `GlobalEvent` 完全相同
63
+
64
+ **适用场景**:
65
+
66
+ - 需要隔离的事件系统
67
+ - 模块内部事件管理
68
+ - 可以整体清空的事件组
69
+
70
+ ### 最佳实践
71
+
72
+ 1. **使用事件名称常量** - 避免字符串拼写错误
73
+
74
+ 2. **及时移除事件** - 在对象销毁时移除监听,避免内存泄漏
75
+
76
+ 3. **使用 target 参数** - 方便批量移除事件
77
+
78
+ 4. **避免事件循环** - 不要在事件 A 中触发事件 B,再在事件 B 中触发事件 A
79
+
80
+ 5. **事件分组管理** - 对于复杂系统,使用独立的 EventManager 实例
81
+
82
+ 详细 API 请查看 `exia-event.d.ts` 类型定义文件。
83
+
84
+ ## 依赖
85
+
86
+ 无外部依赖
87
+
88
+ ## 许可证
89
+
90
+ MIT License
91
+
92
+ ## 作者
93
+
94
+ **xiacg** (xiacg)
95
+ **邮箱**: <xiacg@163.com>
96
+
97
+ ## 源码仓库
98
+
99
+ - [GitHub](https://github.com/xiachenggang/exia-framework.git)
100
+ - [npm](https://www.npmjs.com/package/@xiacg/exia-event)
@@ -0,0 +1,565 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @Description: 命令
5
+ */
6
+ var CommandType;
7
+ (function (CommandType) {
8
+ CommandType[CommandType["Add"] = 1] = "Add";
9
+ CommandType[CommandType["RemoveById"] = 2] = "RemoveById";
10
+ CommandType[CommandType["RemoveByName"] = 3] = "RemoveByName";
11
+ CommandType[CommandType["RemoveByTarget"] = 4] = "RemoveByTarget";
12
+ CommandType[CommandType["RemoveByNameAndTarget"] = 5] = "RemoveByNameAndTarget";
13
+ CommandType[CommandType["ClearAll"] = 6] = "ClearAll";
14
+ })(CommandType || (CommandType = {}));
15
+ /**
16
+ * 命令
17
+ * @internal
18
+ */
19
+ class Command {
20
+ constructor() {
21
+ this.type = CommandType.Add;
22
+ }
23
+ reset() {
24
+ this.type = CommandType.Add;
25
+ this.eventId = 0;
26
+ this.event = null;
27
+ this.name = null;
28
+ this.target = null;
29
+ }
30
+ }
31
+ /**
32
+ * 命令管理器
33
+ * @internal
34
+ */
35
+ class CommandManager {
36
+ constructor() {
37
+ this.commands = [];
38
+ this.index = 0;
39
+ this.length = 0;
40
+ this.clearAll = false;
41
+ }
42
+ /**
43
+ * 添加一个添加事件的命令
44
+ * @param event 事件
45
+ */
46
+ addEvent(event) {
47
+ if (this.index == this.length) {
48
+ let command = new Command();
49
+ command.type = CommandType.Add;
50
+ command.event = event;
51
+ this.commands.push(command);
52
+ this.length++;
53
+ this.index++;
54
+ }
55
+ else {
56
+ let command = this.commands[this.index++];
57
+ command.type = CommandType.Add;
58
+ command.event = event;
59
+ }
60
+ }
61
+ /**
62
+ * 添加一个删除事件或者清理所有的命令
63
+ */
64
+ add(type, eventId, name, target) {
65
+ if (type == CommandType.ClearAll) {
66
+ this.clearAll = true;
67
+ return;
68
+ }
69
+ if (this.index == this.length) {
70
+ let command = new Command();
71
+ command.type = type;
72
+ command.eventId = eventId;
73
+ command.name = name;
74
+ command.target = target;
75
+ this.commands.push(command);
76
+ this.length++;
77
+ this.index++;
78
+ }
79
+ else {
80
+ let command = this.commands[this.index++];
81
+ command.type = type;
82
+ command.eventId = eventId;
83
+ command.name = name;
84
+ command.target = target;
85
+ }
86
+ }
87
+ forEach(callback) {
88
+ if (this.clearAll) {
89
+ callback({
90
+ type: CommandType.ClearAll,
91
+ eventId: 0,
92
+ event: null,
93
+ name: null,
94
+ target: null,
95
+ reset: null,
96
+ });
97
+ return;
98
+ }
99
+ for (let i = 0; i < this.index; i++) {
100
+ let command = this.commands[i];
101
+ callback(command);
102
+ command.reset();
103
+ }
104
+ this.index = 0;
105
+ }
106
+ clear() {
107
+ this.commands.length = 0;
108
+ this.index = 0;
109
+ this.clearAll = false;
110
+ this.length = 0;
111
+ }
112
+ }
113
+
114
+ /** @internal */
115
+ class Event {
116
+ constructor() {
117
+ this.once = false;
118
+ }
119
+ }
120
+
121
+ /** @internal */
122
+ class EventFactory {
123
+ constructor(capacity, objectClass) {
124
+ this._id = 0;
125
+ this._stack = [];
126
+ this._maxCapacity = 64;
127
+ this._maxCapacity = capacity;
128
+ this._msgClass = objectClass;
129
+ }
130
+ allocate() {
131
+ if (this._stack.length == 0) {
132
+ const ret = new this._msgClass();
133
+ ret.id = ++this._id;
134
+ return ret;
135
+ }
136
+ const ret = this._stack.pop();
137
+ // 分配新ID,避免ID重用导致误删
138
+ ret.id = ++this._id;
139
+ return ret;
140
+ }
141
+ recycle(ret) {
142
+ if (this._maxCapacity > 0 && this._stack.length < this._maxCapacity) {
143
+ this._stack.push(ret);
144
+ return true;
145
+ }
146
+ return false;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * @Description: 事件管理器 - 支持递归保护
152
+ */
153
+ /**
154
+ * 最大递归深度常量
155
+ */
156
+ const MAX_RECURSION_DEPTH = 20;
157
+ /**
158
+ * 事件管理器 - 防止递归调用栈溢出
159
+ *
160
+ * 功能特性:
161
+ * - 递归深度限制防止栈溢出
162
+ * - 完全向后兼容现有API
163
+ */
164
+ class EventManager {
165
+ constructor() {
166
+ /**
167
+ * 当前递归发送深度
168
+ * 0 表示没有在发送,> 0 表示正在发送事件
169
+ * @internal
170
+ */
171
+ this.sending_depth = 0;
172
+ /**
173
+ * 注册的所有事件 事件ID -> 事件
174
+ * @internal
175
+ */
176
+ this.events = new Map();
177
+ /**
178
+ * 事件名称 -> 事件ID集合
179
+ * @internal
180
+ */
181
+ this.nameToIds = new Map();
182
+ /**
183
+ * 事件目标 -> 事件ID集合
184
+ * @internal
185
+ */
186
+ this.targetToIds = new Map();
187
+ /**
188
+ * 事件工厂
189
+ * @internal
190
+ */
191
+ this.factory = new EventFactory(64, Event);
192
+ /**
193
+ * 命令管理器
194
+ * @internal
195
+ */
196
+ this.commandManager = new CommandManager();
197
+ }
198
+ /**
199
+ * 添加事件监听器
200
+ * @param name - 事件名称
201
+ * @param callback - 回调函数,当事件触发时执行
202
+ * @param target - 可选参数,指定事件监听的目标对象
203
+ * @returns 返回事件ID,可用于移除事件
204
+ */
205
+ add(name, callback, target) {
206
+ if (!name) {
207
+ throw new Error("事件名称不能为空");
208
+ }
209
+ if (!callback) {
210
+ throw new Error("回调函数不能为空");
211
+ }
212
+ let event = this.factory.allocate();
213
+ event.name = name;
214
+ event.callback = callback;
215
+ event.target = target;
216
+ event.once = false;
217
+ if (this.sending_depth > 0) {
218
+ this.commandManager.addEvent(event);
219
+ return event.id;
220
+ }
221
+ this._addEvent(event);
222
+ return event.id;
223
+ }
224
+ /**
225
+ * 添加一个只触发一次的事件监听器
226
+ * @param name - 事件名称
227
+ * @param callback - 事件触发时要执行的回调函数
228
+ * @param target - 可选参数,指定事件监听器的目标对象
229
+ * @returns 返回事件ID,可用于移除事件
230
+ */
231
+ addOnce(name, callback, target) {
232
+ if (!name) {
233
+ throw new Error("事件名称不能为空");
234
+ }
235
+ if (!callback) {
236
+ throw new Error("回调函数不能为空");
237
+ }
238
+ let event = this.factory.allocate();
239
+ event.name = name;
240
+ event.callback = callback;
241
+ event.target = target;
242
+ event.once = true;
243
+ if (this.sending_depth > 0) {
244
+ this.commandManager.addEvent(event);
245
+ return event.id;
246
+ }
247
+ this._addEvent(event);
248
+ return event.id;
249
+ }
250
+ /**
251
+ * 添加事件内部方法
252
+ * @param event 事件对象
253
+ * @internal
254
+ */
255
+ _addEvent(event) {
256
+ this.events.set(event.id, event);
257
+ if (!this.nameToIds.has(event.name)) {
258
+ this.nameToIds.set(event.name, new Set());
259
+ }
260
+ const ids = this.nameToIds.get(event.name);
261
+ ids.add(event.id);
262
+ let target = event.target;
263
+ if (target) {
264
+ if (!this.targetToIds.has(target)) {
265
+ this.targetToIds.set(target, new Set());
266
+ }
267
+ this.targetToIds.get(target).add(event.id);
268
+ }
269
+ }
270
+ /**
271
+ * 发送事件给所有注册的监听器(带递归保护)
272
+ * @param name - 事件名称
273
+ * @param target - 可选参数,指定目标对象,只有目标对象匹配时才会触发监听器
274
+ * @param args - 传递给监听器回调函数的参数
275
+ */
276
+ send(name, target, ...args) {
277
+ // 递归深度保护:阻止超过限制的执行并报告错误
278
+ if (this.sending_depth >= MAX_RECURSION_DEPTH) {
279
+ console.error(`[EventManager] 递归深度超出限制!`);
280
+ console.error(` 事件名称: "${name}"`);
281
+ console.error(` 当前深度: ${this.sending_depth}`);
282
+ console.error(` 最大深度: ${MAX_RECURSION_DEPTH}`);
283
+ console.error(` 为防止栈溢出,事件执行已被阻止`);
284
+ return;
285
+ }
286
+ if (!this.nameToIds.has(name)) {
287
+ return;
288
+ }
289
+ const eventIds = this.nameToIds.get(name);
290
+ if (eventIds.size === 0) {
291
+ return;
292
+ }
293
+ // 使用局部变量,避免嵌套调用时互相污染
294
+ const needRemoveIds = [];
295
+ const triggerList = [];
296
+ // 增加递归深度
297
+ this.sending_depth++;
298
+ // 构建触发列表
299
+ for (const eventId of eventIds.values()) {
300
+ if (!this.events.has(eventId)) {
301
+ needRemoveIds.push(eventId);
302
+ continue;
303
+ }
304
+ let event = this.events.get(eventId);
305
+ if (!target || target === event.target) {
306
+ triggerList.push(event);
307
+ if (event.once) {
308
+ needRemoveIds.push(eventId);
309
+ }
310
+ }
311
+ }
312
+ // 同步触发事件
313
+ for (const event of triggerList) {
314
+ event.callback(...args);
315
+ }
316
+ // 减少递归深度
317
+ this.sending_depth--;
318
+ // 只在最外层执行清理工作
319
+ if (this.sending_depth === 0) {
320
+ // 清理 once 事件
321
+ if (needRemoveIds.length > 0) {
322
+ for (const id of needRemoveIds) {
323
+ this.remove(id);
324
+ }
325
+ }
326
+ // 处理命令队列
327
+ this.commandManager.forEach((command) => {
328
+ switch (command.type) {
329
+ case CommandType.Add:
330
+ this._addEvent(command.event);
331
+ break;
332
+ case CommandType.RemoveById:
333
+ this.remove(command.eventId);
334
+ break;
335
+ case CommandType.RemoveByName:
336
+ this.removeByName(command.name);
337
+ break;
338
+ case CommandType.RemoveByTarget:
339
+ this.removeByTarget(command.target);
340
+ break;
341
+ case CommandType.RemoveByNameAndTarget:
342
+ this.removeByNameAndTarget(command.name, command.target);
343
+ break;
344
+ case CommandType.ClearAll:
345
+ this.clearAll();
346
+ break;
347
+ }
348
+ });
349
+ }
350
+ }
351
+ /**
352
+ * 通过事件ID移除事件
353
+ * @param eventId 事件ID
354
+ */
355
+ remove(eventId) {
356
+ if (!this.events.has(eventId)) {
357
+ return;
358
+ }
359
+ if (this.sending_depth > 0) {
360
+ this.commandManager.add(CommandType.RemoveById, eventId, null, null);
361
+ return;
362
+ }
363
+ let event = this.events.get(eventId);
364
+ let name = event.name;
365
+ let target = event.target;
366
+ this.events.delete(eventId);
367
+ this.factory.recycle(event);
368
+ if (this.nameToIds.has(name)) {
369
+ this.nameToIds.get(name).delete(eventId);
370
+ }
371
+ if (target && this.targetToIds.has(target)) {
372
+ this.targetToIds.get(target).delete(eventId);
373
+ }
374
+ }
375
+ /**
376
+ * 移除指定名称的所有事件
377
+ * @param name 事件名称
378
+ */
379
+ removeByName(name) {
380
+ if (!this.nameToIds.has(name)) {
381
+ return;
382
+ }
383
+ let eventIds = this.nameToIds.get(name);
384
+ if (eventIds.size === 0) {
385
+ return;
386
+ }
387
+ if (this.sending_depth > 0) {
388
+ this.commandManager.add(CommandType.RemoveByName, null, name, null);
389
+ return;
390
+ }
391
+ eventIds.forEach((eventId) => {
392
+ if (this.events.has(eventId)) {
393
+ let event = this.events.get(eventId);
394
+ if (event.target && this.targetToIds.has(event.target)) {
395
+ this.targetToIds.get(event.target).delete(eventId);
396
+ }
397
+ this.events.delete(eventId);
398
+ this.factory.recycle(event);
399
+ }
400
+ });
401
+ this.nameToIds.delete(name);
402
+ }
403
+ /**
404
+ * 移除指定目标的所有事件
405
+ * @param target 目标对象
406
+ */
407
+ removeByTarget(target) {
408
+ if (!this.targetToIds.has(target)) {
409
+ return;
410
+ }
411
+ let eventIds = this.targetToIds.get(target);
412
+ if (eventIds.size === 0) {
413
+ return;
414
+ }
415
+ if (this.sending_depth > 0) {
416
+ this.commandManager.add(CommandType.RemoveByTarget, null, null, target);
417
+ return;
418
+ }
419
+ eventIds.forEach((eventId) => {
420
+ if (this.events.has(eventId)) {
421
+ let event = this.events.get(eventId);
422
+ if (this.nameToIds.has(event.name)) {
423
+ this.nameToIds.get(event.name).delete(eventId);
424
+ }
425
+ this.events.delete(eventId);
426
+ this.factory.recycle(event);
427
+ }
428
+ });
429
+ this.targetToIds.delete(target);
430
+ }
431
+ /**
432
+ * 移除指定名称和指定目标的事件
433
+ * @param name 事件名称
434
+ * @param target 绑定的目标对象
435
+ */
436
+ removeByNameAndTarget(name, target) {
437
+ if (!this.nameToIds.has(name)) {
438
+ return;
439
+ }
440
+ let nameIds = this.nameToIds.get(name);
441
+ let targetIds = this.targetToIds.get(target);
442
+ // 检查targetIds是否存在,避免NPE
443
+ if (nameIds.size === 0 || !targetIds || targetIds.size === 0) {
444
+ return;
445
+ }
446
+ if (this.sending_depth > 0) {
447
+ this.commandManager.add(CommandType.RemoveByNameAndTarget, null, name, target);
448
+ return;
449
+ }
450
+ // 使用局部变量
451
+ const needRemoveIds = [];
452
+ if (nameIds.size < targetIds.size) {
453
+ nameIds.forEach((eventId) => {
454
+ let event = this.events.get(eventId);
455
+ if (event.target === target) {
456
+ needRemoveIds.push(eventId);
457
+ }
458
+ });
459
+ }
460
+ else {
461
+ targetIds.forEach((eventId) => {
462
+ let event = this.events.get(eventId);
463
+ if (event.name === name) {
464
+ needRemoveIds.push(eventId);
465
+ }
466
+ });
467
+ }
468
+ if (needRemoveIds.length > 0) {
469
+ for (const id of needRemoveIds) {
470
+ this.remove(id);
471
+ }
472
+ }
473
+ }
474
+ /**
475
+ * 清空所有注册的事件
476
+ */
477
+ clearAll() {
478
+ if (this.sending_depth > 0) {
479
+ this.commandManager.add(CommandType.ClearAll, null, null, null);
480
+ return;
481
+ }
482
+ for (const event of this.events.values()) {
483
+ this.factory.recycle(event);
484
+ }
485
+ this.events.clear();
486
+ this.nameToIds.clear();
487
+ this.targetToIds.clear();
488
+ this.commandManager.clear();
489
+ // 清理递归保护相关状态
490
+ this.sending_depth = 0;
491
+ }
492
+ }
493
+
494
+ /**
495
+ * @Description: 全局事件
496
+ */
497
+ class GlobalEvent {
498
+ /**
499
+ * 添加一个事件
500
+ * @param name 事件名称
501
+ * @param callback 事件回调
502
+ * @param target 事件目标
503
+ */
504
+ static add(name, callback, target) {
505
+ return this.event.add(name, callback, target);
506
+ }
507
+ /**
508
+ * 添加一个只触发一次的事件
509
+ */
510
+ static addOnce(name, callback, target) {
511
+ return this.event.addOnce(name, callback, target);
512
+ }
513
+ /**
514
+ * 发送一个事件
515
+ * @param name 事件名称
516
+ * @param args 事件参数
517
+ */
518
+ static send(name, ...args) {
519
+ this.event.send(name, null, ...args);
520
+ }
521
+ /**
522
+ * 发送一个事件给指定目标
523
+ * @param name 事件名称
524
+ * @param target 事件目标
525
+ * @param args 事件参数
526
+ */
527
+ static sendToTarget(name, target, ...args) {
528
+ this.event.send(name, target, ...args);
529
+ }
530
+ /**
531
+ * 移除一个指定ID的事件
532
+ * @param eventId 事件ID
533
+ */
534
+ static remove(eventId) {
535
+ this.event.remove(eventId);
536
+ }
537
+ static removeByName(name) {
538
+ this.event.removeByName(name);
539
+ }
540
+ static removeByTarget(target) {
541
+ this.event.removeByTarget(target);
542
+ }
543
+ /**
544
+ * 通过目标和事件名称批量移除事件
545
+ * @param name 事件名称
546
+ * @param target 事件目标
547
+ */
548
+ static removeByNameAndTarget(name, target) {
549
+ this.event.removeByNameAndTarget(name, target);
550
+ }
551
+ /**
552
+ * 清空所有事件
553
+ */
554
+ static clearAll() {
555
+ this.event.clearAll();
556
+ }
557
+ }
558
+ /**
559
+ * 事件管理器
560
+ * @internal
561
+ */
562
+ GlobalEvent.event = new EventManager();
563
+
564
+ exports.EventManager = EventManager;
565
+ exports.GlobalEvent = GlobalEvent;