3.6 KiB
3.6 KiB
Event
命名空间: XCEngine::Core
类型: class template
头文件: XCEngine/Core/Event.h
描述: 一个轻量模板事件广播器,支持订阅、延迟取消订阅和参数化回调调用。
角色概述
Event<Args...> 是当前代码库里最常用的基础广播机制之一。它的定位很明确:
- 用模板参数表达回调签名
- 用
Subscribe()返回监听器 ID - 用
Invoke()把参数广播给所有当前监听器
和一些商业引擎里的事件系统相比,它更偏轻量 C++ 容器封装,而不是完整的消息总线、typed dispatcher 或 editor event graph。
当前真实使用
当前已能在多个核心模块里看到它的直接使用,例如:
Scene::OnGameObjectCreated()Scene::OnGameObjectDestroyed()SceneManager的场景切换事件InputManager的键鼠输入事件
这说明它不是“理论上的基础设施”,而是已经进入主代码路径的常用类型。
当前实现行为
1. Subscribe() 会分配递增 ID
- 监听器以
(uint64_t id, Callback)形式保存在m_listeners中 - ID 由
m_nextId递增生成 tests/core/test_core.cpp已验证不同订阅返回的 ID 不同
2. Unsubscribe() 是延迟取消订阅
Unsubscribe(id) 当前不会立刻从 m_listeners 删除,而是先把 ID 放进 m_pendingUnsubscribes。
真正删除发生在两种时机:
- 显式调用 ProcessUnsubscribes
- 下一次 Invoke 开始前
这意味着 API 语义更接近“标记取消订阅”,而不是“立刻从容器抹掉”。
3. Invoke() 会在锁外执行回调
Invoke() 的当前流程是:
- 加锁
- 先处理
m_pendingUnsubscribes - 复制
m_listeners到本地listenersCopy - 释放锁
- 逐个执行回调
这套做法的关键收益是:
- 回调执行期间不会长期占着互斥锁
- 回调内部即使再次订阅/取消订阅,也不直接破坏当前这次遍历
代价是会有一次监听器数组复制,且它不是为极端高频无分配场景优化的实现。
线程语义
已有保护
Subscribe()、Unsubscribe()、ProcessUnsubscribes()、Invoke()、Clear()都会访问m_mutex
不能误解的地方
所以可以说:它对常规订阅和调用路径做了基础互斥保护,但并不是“所有访问方式都线程安全”的容器。
当前实现限制
begin()/end()暴露的是内部容器迭代器,不适合并发或长时间持有。- 回调列表在
Invoke()时会拷贝一份,适合易用性,但不适合把它当成极致性能事件系统。 - 没有 listener priority、事件消费中止或过滤器机制。
- 没有 scoped subscription / RAII subscription token。
测试覆盖
tests/core/test_core.cpp 已覆盖:
- 订阅与回调调用
- 带参数事件
- 取消订阅
- 清空监听器
- 多监听器广播
- 返回 ID 唯一性