Event — 事件系统

RoninNetease 事件系统在网易引擎原生事件机制基础上,提供了责任链模式、装饰器注册和强类型事件对象。


1. 架构概览

architect.event
├── core.py          ← EventChain, ChainedEvent, EventSignal, EventTarget, Delegate
├── client.py        ← 客户端事件注册表(EventChain 实例池)
├── server.py        ← 服务端事件注册表
└── events/
    ├── player.py    ← 玩家相关事件
    ├── entity.py    ← 实体相关事件
    ├── block.py     ← 方块相关事件
    ├── item.py      ← 物品相关事件
    ├── world.py     ← 世界相关事件
    ├── control.py   ← UI 控制事件
    ├── interface.py ← UI 接口事件
    ├── lobby.py     ← 大厅事件
    ├── model.py     ← 模型相关事件
    ├── physx.py     ← 物理相关事件
    ├── setting.py   ← 设置相关事件
    ├── sound.py     ← 音效事件
    └── ui.py        ← UI 事件

2. 核心类

2.1 ChainedEvent — 事件对象

框架对引擎原生事件的包装,提供了统一的访问和中断接口:

class ChainedEvent(object):
    def __init__(self, eventType, data={}, interruptRef=Ref(None))
    def stop()           # 停止事件传递
    def prevent()        # 取消默认行为(设置 cancel=True, ret=True)
    def setEvent(key, value)   # 修改事件字段
    def updateEvent(dict)      # 批量更新字段
    def dict()           # 获取内部 dict
    def clone()          # 克隆事件副本
    def __getattr__(name)  # 属性访问 → data[name]

使用示例:

class MySystem(ServerSubsystem):
    @EventListener('EntityHurtEvent')
    def onEntityHurt(self, event):
        # 属性式访问
        entityId = event.id
        damage = event.damage
        source = event.srcId

        # 修改事件
        event.setEvent('damage', 0)

        # 停止传递
        if entityId == self.protected_id:
            event.stop()

        # 取消默认行为
        event.prevent()

        # 获取原始 dict
        raw = event.dict()

2.2 EventChain — 事件链(责任链模式)

class EventChain(Unreliable):
    evType: str              # 事件类型名
    guarded: bool = True     # 是否保护模式(前序监听器异常则中断)
    useCapture: bool = False # 是否使用捕获模式

    def capture(fn)          # 添加捕获阶段监听器(先添加先执行)
    def addListener(fn)      # 添加冒泡阶段监听器(先添加后执行)
    def removeListener(fn)   # 移除监听器
    def dispatch(_ev)        # 分发事件

执行顺序:

2.3 EventSignal — 信号(观察者模式)

class EventSignal(Unreliable):
    def on(fn)           # 注册处理器
    def off(fn)          # 注销处理器
    def emit(*args)      # 触发所有处理器

用于框架内部生命周期信号(INITIALIZEDPRELOADED)以及 Marker 的 onEntityCreated/onEntityDestroyed

2.4 Delegate — 单回调委托

class Delegate(Unreliable):
    def bind(fn)         # 绑定函数
    def call(*args)      # 调用
    def unbind()         # 解绑
    def __call__(*args)  # 可直接调用

2.5 EventTarget — 命名事件目标

class EventTarget(object):
    def addListener(event, fn)    # 添加监听
    def removeListener(event, fn) # 移除
    def removeAllListener()       # 全部移除
    def dispatch(event, *args)    # 触发事件

3. 事件注册方式

3.1 @EventListener 装饰器(引擎事件)

from architect.core import EventListener

class MySystem(ServerSubsystem):
    @EventListener('ServerPostInitEvent')
    def onServerPostInit(self, event):
        """引擎事件,event 为 ChainedEvent"""
        pass

    @EventListener('EntityHurtEvent')
    def onEntityHurt(self, event):
        entityId = event.id
        damage = event.damage

装饰器签名:

def EventListener(eventType=None, isCustomEvent=False):
    """
    :param eventType: 事件类型字符串,None 则从默认事件类推断
    :param isCustomEvent: True 时为自定义事件,注册到自定义事件通道
    """

注册流程:

  1. @EventListener 在方法上标记 _event_listener 注解
  2. Subsystem._init()_addListeners() 扫描所有带注解的方法
  3. 通过 Subsystem._addListener() 注册到引擎事件系统

3.2 @CustomEvent 装饰器(自定义事件)

from architect.core import CustomEvent

class MySystem(ClientSubsystem):
    @CustomEvent('MyDataSyncEvent')
    def onDataSync(self, event):
        data = event['data']
        print('Received:', data)

@CustomEvent@EventListener(eventType, isCustomEvent=True) 的别名。

3.3 编程式注册

class MySystem(ServerSubsystem):
    def onInit(self):
        # 自定义事件
        self.on('MyEvent', self.onMyEvent, isCustomEvent=True)

        # 引擎事件
        self.listen('EntityHurtEvent', self.onEntityHurt)

    def onMyEvent(self, event):
        pass

    def onDestroy(self):
        # 注销
        self.off('MyEvent', self.onMyEvent, isCustomEvent=True)
        self.unlisten('EntityHurtEvent', self.onEntityHurt)

4. 事件注册表 — architect.event.client / architect.event.server

框架为每种引擎事件类型维护了 EventChain 单例:

from architect.event import event as eventClient  # 客户端
from architect.event import event as eventServer  # 服务端

# event 是 __getattr__ 拦截器,返回对应事件类型的 EventChain
chain = eventClient('EntityHurtEvent', isCustom=False)
chain.addListener(my_handler)

event 对象的行为:

事件类型自动生成(可选)

框架在 architect/event/events/ 下提供了预定义事件类文件,每个文件定义事件类型类:

# architect/event/events/player.py
class ServerPlayerDieEvent:
    """玩家死亡事件"""
    pass

class EntityHurtEvent:
    """实体受伤事件"""
    pass

@EventListener 不传参时,会从方法的第一个非 self 参数的默认值中提取事件类名:

from architect.event import events

class BlockProtection(ServerSubsystem):
    # 不传 eventType,从 ev 的默认值 events.EntityHurtEvent() 推断事件类型为 "EntityHurtEvent"
    @EventListener()
    def onEntityHurt(self, ev=events.EntityHurtEvent()):
        targetId = ev.id
        if self.isProtected(targetId):
            ev.setEvent('damage', 0)
            ev.prevent()

    # 同样,自动推断为 "ServerPlayerDieEvent"
    @EventListener()
    def onPlayerDie(self, ev=events.ServerPlayerDieEvent()):
        playerId = ev.id
        self.dropInventory(playerId)

注意: 默认值必须是一个事件类实例。装饰器会调用 default.__class__.__name__ 来获取事件类型字符串。如果方法的第一个非 self 参数没有默认值,或默认值为 None,会抛出 ValueError


5. 事件分发流程

引擎事件触发
    │
    ▼
SubsystemManager.addListener() 或 Subsystem.on()
    │  调用 system.ListenForEvent(engine, sysName, eventType, listener, fn)
    │
    ▼
EventChain.dispatch(eventData)
    ├── 创建 ChainedEvent(eventType, eventData, interruptRef=Ref(None))
    ├── 按 capture/bubble 顺序遍历 handlers
    │   └── tryCall(handler, ChainedEvent)
    │       ├── handler 内调用 event.stop()
    │       │   → interruptRef.value = True → 中断后续 handler
    │       └── handler 内调用 event.prevent()
    │           → eventData['cancel'] = True
    │
    └── guarded=True 时,tryCall 捕获异常 → 停止传递

6. EventSignal 使用(生命周期信号)

from architect.core.subsystem import SubsystemManager

manager = SubsystemManager.getInstance()

# 监听初始化完成
manager.INITIALIZED.on(lambda: print('Plugins loaded'))

# 监听预加载完成
manager.PRELOADED.on(lambda: print('All subsystems ready'))

# 注销
manager.INITIALIZED.off(callback)

7. 与 @Sched.Event 的区别

# 事件监听器(接收 ChainedEvent)
@EventListener('EntityHurtEvent')
def onHurt(self, event):
    target = event.id
    damage = event.damage

# 事件调度器(不接收参数,通过 EventReader 获取)
@Sched.Event('EntityHurtEvent')
def onHurtSched(self):
    reader = getComponent(self.entityId, EventReader)
    event = reader.ev
    target = event['id']

8. 最佳实践

  1. 使用装饰器注册,避免在 onInit 中手动调用 self.on()
  2. 使用 ChainedEvent 属性访问,它比 dict 访问更易读:
    event.id  # 推荐
    event['id']  # 也支持
    
  3. 在不需要中断事件流时不要调用 stop()prevent()
  4. 自定义事件名称使用语义化前缀,如 OnPlayerLevelUp 而非 Ev1
  5. 客户端和服务端事件是分离的@EventListener 在服务端子系统中只监听服务端事件

下一步