Files
XCEngine/docs/api/XCEngine/Core/Event/Event.md

3.6 KiB
Raw Blame History

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

真正删除发生在两种时机:

这意味着 API 语义更接近“标记取消订阅”,而不是“立刻从容器抹掉”。

3. Invoke() 会在锁外执行回调

Invoke() 的当前流程是:

  1. 加锁
  2. 先处理 m_pendingUnsubscribes
  3. 复制 m_listeners 到本地 listenersCopy
  4. 释放锁
  5. 逐个执行回调

这套做法的关键收益是:

  • 回调执行期间不会长期占着互斥锁
  • 回调内部即使再次订阅/取消订阅,也不直接破坏当前这次遍历

代价是会有一次监听器数组复制,且它不是为极端高频无分配场景优化的实现。

线程语义

已有保护

  • Subscribe()Unsubscribe()ProcessUnsubscribes()Invoke()Clear() 都会访问 m_mutex

不能误解的地方

  • begin / end 直接返回内部 std::vector 迭代器,没有加锁包装
  • 如果你跨线程持有这些迭代器,同时别的线程订阅/取消订阅,就没有安全保证

所以可以说:它对常规订阅和调用路径做了基础互斥保护,但并不是“所有访问方式都线程安全”的容器。

当前实现限制

  • begin() / end() 暴露的是内部容器迭代器,不适合并发或长时间持有。
  • 回调列表在 Invoke() 时会拷贝一份,适合易用性,但不适合把它当成极致性能事件系统。
  • 没有 listener priority、事件消费中止或过滤器机制。
  • 没有 scoped subscription / RAII subscription token。

测试覆盖

tests/core/test_core.cpp 已覆盖:

  • 订阅与回调调用
  • 带参数事件
  • 取消订阅
  • 清空监听器
  • 多监听器广播
  • 返回 ID 唯一性

相关方法

相关指南

相关文档