Subsystem — 子系统

子系统(Subsystem)是 RoninNetease 框架中业务逻辑的基本组织单元。每个子系统封装一组相关功能,由 SubsystemManager 统一管理生命周期。


1. 类层次结构

Subsystem (base, abstract)
├── ServerSubsystem   ← 服务端子系统基类
└── ClientSubsystem   ← 客户端子系统基类

所有子系统必须继承 ServerSubsystemClientSubsystem,不能直接继承 Subsystem


2. 创建子系统

2.1 装饰器注册(推荐)

from architect.core import SubsystemServer, ServerSubsystem
from architect.core import Sched, EventListener, CustomEvent

@SubsystemServer  # 标记为服务端子系统,自动注册
class MySystem(ServerSubsystem):
    canTick = True

    def onInit(self):
        print('System initialized')

    def onReady(self):
        print('All systems ready')

    def onUpdate(self, dt):
        print('Tick:', dt)
from architect.core import SubsystemClient, ClientSubsystem

@SubsystemClient  # 标记为客户端子系统,自动注册
class MyClientSystem(ClientSubsystem):
    canTick = True

    def onRender(self, dt):
        print('Render frame:', dt)

2.2 注册时机

@SubsystemServer / @SubsystemClient 装饰器在模块被 Import 时生效。装饰器内部调用 LoaderUtils.getManager() 获取当前 SubsystemManager,然后调用 manager.registerSubsystem(cls)

注意: 装饰器调用时 SubsystemManager 必须已存在。如果通过 modMain.pycreateServer() / createClient() 正常启动,管理器会在导入用户模块之前创建。


3. 生命周期

@SubsystemServer 装饰 → registerSubsystem(cls) [加入注册列表]
        │
        ▼
_initManager(isHost) → _addAnnotatedSubsystems()
        │
        ├── SubsystemManager.addSubsystem(cls)
        │       └── 实例化 cls(system, engine, sysName)
        │               └── _init()
        │                       ├── _addListeners()       扫描 @EventListener/@CustomEvent 并注册
        │                       ├── _addSchedMethods()    扫描 @Sched.* 并添加调度任务
        │                       ├── _registerRemoteFuncs()扫描 @Remote 并注册
        │                       └── onInit()              【用户钩子:子系统创建完毕】
        │
        ▼
_callReady(isHost)
        └── 所有子系统 onReady()    【用户钩子:所有子系统就绪,可跨系统通信】
        │
        ▼
startTicking(isHost) → tickSubsystem()
        └── 每帧调用 onUpdate(dt)   【用户钩子:游戏 Tick】(需 canTick=True)
        └── 每帧调用 tickSched.executeSequence()
                ├── TIMER_TASK
                ├── BeforeUpdate
                ├── Update
                └── AfterUpdate

[客户端] startTicking() → tickRender()
        └── 每帧调用 onRender(dt)   【用户钩子:渲染帧】
        └── 每帧调用 renderSched.executeSequence()

[销毁时]
        └── onDestroy()              【用户钩子:清理资源】

3.1 生命周期钩子

钩子 调用时机 用途
onInit() 子系统实例创建后立即调用 初始化内部状态,此时只有当前子系统可用
onReady() 所有子系统创建完毕后调用 跨子系统通信初始化,获取其他子系统引用
onUpdate(dt) 每游戏 Tick(需 canTick=True 游戏逻辑更新
onRender(dt) 每渲染帧(仅客户端) 渲染相关逻辑
onDestroy() 子系统销毁时 清理资源

3.2 原子初始化

从 v1.1.0 开始,Subsystem._init() 实现了原子回滚:

如果在 _addListeners()_addSchedMethods()_registerRemoteFuncs() 中任一操作失败,已注册的资源会被自动清理,子系统从管理器中移除。这防止了"僵尸子系统"状态。


4. 核心属性

Subsystem 基类属性

属性 类型 说明
system _ShadowSystemServer | _ShadowSystemClient 引擎底层 System 实例
engine str 引擎命名空间
sysName str 引擎系统名称
ticks int 已执行的 Tick 次数
canTick bool 是否启用 onUpdate 回调(默认 False
initialized bool 是否已初始化
fixedSchedulers dict 已启动的固定频率调度器映射

5. 事件通信

5.1 监听引擎事件

class MySystem(ServerSubsystem):
    @EventListener('ServerPostInitEvent')
    def onServerPostInit(self, event):
        """event 是 ChainedEvent 实例"""
        pass

    @EventListener('EntityHurtEvent')
    def onEntityHurt(self, event):
        entityId = event.id          # 等价于 event['id']
        damage = event.damage        # 等价于 event['damage']

5.2 监听自定义事件

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

5.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 onEntityHurt(self, event):
        pass

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

6. 跨端通信

6.1 服务端 → 客户端

class MyServerSystem(ServerSubsystem):
    def syncToClient(self, playerId):
        self.sendClient(playerId, 'OnSyncData', {'hp': 100})

    def broadcast(self):
        self.sendAllClients('OnGlobalEvent', {'message': 'hello'})
方法 说明
sendClient(targetIds, eventName, eventData) 发送给指定玩家(str 或 int 发单人,list 发多人)
sendAllClients(eventName, eventData) 广播给所有客户端

6.2 客户端 → 服务端

class MyClientSystem(ClientSubsystem):
    def sendToServer(self):
        self.sendServer('OnClientAction', {'action': 'jump'})
方法 说明
sendServer(eventName, eventData) 发送消息给服务端

6.3 广播事件

# 在子系统内向自身监听的玩家广播(引擎层面)
self.broadcast('MyEvent', {'data': 42})

7. 实体操作

7.1 服务端

class MyServerSystem(ServerSubsystem):
    def spawn(self):
        from architect.core import Location
        from architect.core import isServer
        # 按字符串模板生成
        entityId = self.spawnEntity(
            'minecraft:zombie',
            Location((0, 64, 0), 0),
            (0, 0),           # rotation (yaw, pitch)
            isNpc=False,
            isGlobal=False
        )
        # 按 NBT 数据生成
        entityId = self.spawnEntity(
            {'identifier': 'minecraft:zombie', 'nbt': {}},
            Location((0, 64, 0), 0),
            (0, 0)
        )

    def spawnItem(self):
        from architect.core import Location
        self.spawnItem(
            {'item': 'minecraft:diamond', 'count': 1},
            Location((0, 64, 0), 0)
        )

    def destroy(self, entityId):
        self.destroyEntity(entityId)

7.2 客户端

class MyClientSystem(ClientSubsystem):
    def spawn(self):
        entityId = self.spawnEntity(
            'minecraft:zombie',
            (0, 64, 0),    # pos
            (0, 0)         # rot
        )

    def destroy(self, entityId):
        self.destroyEntity(entityId)

    def create_effect(self):
        self.createSfx('effects/explosion', (0, 0, 0))
        self.createParticle('minecraft:flame', (0, 64, 0))
        self.createEffectBind('effects/heal', entityId, 'animation.stand')
        self.destroySfx(sfx_id)

8. 调度系统集成

class MySystem(ServerSubsystem):
    canTick = True

    @Sched.Tick()
    def onTick(self):
        """每帧在 Update 阶段执行"""

    @Sched.Tick(SchedUpdateFlags.BeforeUpdate)
    def beforeUpdate(self):
        """在 Update 之前"""

    @Sched.Fixed('my_fixed')
    def onFixedUpdate(self):
        """固定频率执行"""

    def onReady(self):
        self.scheduleFixed('my_fixed', period=1.0)  # 1秒间隔

    @Sched.Event('EntityHurtEvent')
    def onEntityHurt(self):
        """事件触发时执行"""

@Sched.Fixed 注册的方法需要在 onReady() 中通过 scheduleFixed() 启动调度器。不要onInit() 中调用 scheduleFixed(),因为此时引擎尚未完全初始化。


9. 获取其他子系统

9.1 通过管理器

class MySystem(ServerSubsystem):
    def onReady(self):
        manager = SubsystemManager.getInstance()
        other = manager.getSubsystem(OtherSystem)
        other.do_something()

9.2 通过静态工具类

class MySystem(ServerSubsystem):
    def onReady(self):
        # 按类名获取
        other = SubsystemManager.getInstance().getSubsystemByName('OtherSystem')

9.3 通过子系统类方法

other = OtherSystem.getInstance()  # 内部调用了 SubsystemManager

10. subsystem 静态工具类

subsystem 是一个静态工具类,代理「第一个注册的子系统」的常用方法。适合在未持有子系统引用时快速调用:

from architect.core.subsystem import subsystem

# 客户端 → 服务端
subsystem.sendServer('MyEvent', {'data': 42})

# 服务端 → 指定客户端
subsystem.sendClient('player123', 'MyEvent', {'data': 42})

# 服务端 → 所有客户端
subsystem.sendAllClients('MyEvent', {'data': 42})

# 生成实体
subsystem.spawnServerEntity('minecraft:zombie', Location((0,64,0), 0), (0,0))
subsystem.spawnClientEntity('minecraft:zombie', (0,64,0), (0,0))

# 监听事件
subsystem.addListener('MyEvent', handler, isCustomEvent=True)
subsystem.removeListener('MyEvent', handler, isCustomEvent=True)

注意: subsystem 指向的是第一个被注册的子系统。如果模组有多个子系统,推荐通过 SubsystemManager.getInstance().getSubsystem() 获取精确的子系统实例。


11. Internal 标记

from architect.core.subsystem import Internal

class MySystem(ServerSubsystem):
    @Internal
    def _privateHelper(self):
        """标记为内部方法,不会被框架处理为事件或调度"""
        pass

@Internal 装饰器在方法上设置 _internal_method 注解,阻止框架将其作为事件监听器或调度目标处理。


下一步