KBEngine 文档KBEngine 文档
首页
源码学习
架构
API
资料
指南
GitHub
首页
源码学习
架构
API
资料
指南
GitHub
  • Part I 为什么长这样

    • 源码学习首页
    • 1. 导读与阅读方法
    • 2. BigWorld:问题、模型与核心概念
    • 3. KBEngine 系统全景
  • Part II 运行骨架

    • 4. 启动流程与进程模型
    • 5. EntityDef 与实体定义系统
    • 6. Python 运行时与脚本桥接
  • Part III 基础设施层

    • 7. 并发模型、线程与内存基础设施
    • 8. 网络基础设施:I/O 模型与进程间通信
    • 9. 分布式基础:ID、发现、注册与一致性
  • Part IV 通信与协作

    • 10. 序列化、Bundle 与网络消息
    • 11. RPC、EntityCall 与通信模式
    • 12. 属性同步与数据包广播
    • 13. 数据库、DBMgr 与持久化
  • Part V 空间、运动与拓扑

    • 14. Space、AOI 与视野系统
    • 15. 空间拓扑与动态扩容
    • 16. 移动、寻路与导航
    • 17. Ghost 系统
  • Part VI 脚本层行为

    • 18. 钩子、回调、定时器与事件
  • Part VII 前后端交互

    • 19. 客户端协议与前后端交互
  • Part VIII 运维、调试与稳定性

    • Ch20 可观测性:监控、性能分析与调试
    • Ch21 热更新、容错与运维工具
  • Part IX 串联与实战

    • Ch22 玩家完整生命周期
    • Ch23 BigWorld 与 KBEngine 对照
    • Ch24 实战源码走读
  • 阅读辅助

    • 全部目录
  • Appendix

    • 附录 A 源码阅读地图与下一步
    • 附录 B 关键算法速查
    • 附录 C 外部参考系统速查
    • 附录 D 专业术语速查
    • 附录 E 引擎适用场景与游戏类型选型指南
    • 附录 F 坐标系约定:BigWorld 与 KBEngine
    • 附录 G 服务器时间管理与世界时钟

2. BigWorld:问题、模型与核心概念

这一章不打算把 BigWorld 写成一段产品史。目标是让读者先建立"为什么会长成 Login / Base / Cell / DB 这套模型"的心智框架,然后把 Entity / Proxy / Space / Witness / Ghost 这些概念放回同一个运行模型里理解。

2.1 本章核心问题

  • 大规模在线世界到底遇到了哪些传统游戏服务器很难承受的问题?
  • 为什么这些问题最后会逼出 Login / Base / Cell / DB 四类分工?
  • Entity 为什么一定会出现"多侧表示"?
  • Proxy / Space / Witness / Ghost 在同一个运行模型里各自扮演什么角色?

2.2 先从问题出发,而不是先背术语

BigWorld 一系技术路线真正要解决的,不是"服务器怎么写得更优雅",而是几类很具体的工程约束:

  • 在线人数高:单进程难以稳定承载所有玩家状态,一台机器的内存/CPU/网络都有上限
  • 空间实时性:场景内位置、可见性、移动和战斗是高频实时问题,tick 频率 10Hz(100ms 预算)
  • 长期状态管理:场景外账号、背包、任务等逻辑更接近长期状态管理,不需要实时空间
  • 持久化隔离:数据既要持久化,又不能让数据库成为实时状态来源
  • 会话连续性:玩家会掉线、重连、切图、跨进程迁移,在线状态不能一断就全丢

把这些问题放在一起看,就会发现:

  • "一个玩家对象"并不足以表达所有运行态
  • "一个服务器进程"也不足以承载整个世界

BigWorld 的价值不在于发明了一堆术语,而在于把这些约束组织成了一套能长期运行的模型。

2.3 为什么会形成 Login / Base / Cell / DB

把问题拆开,就会自然推出几种不同职责:

接入问题

客户端第一跳接入、认证、分发,不应该和玩家长期运行态绑死。于是有了:

LoginApp — 尽量不承载玩家长期业务状态的接入网关,负责认证、转发和登录阶段的临时会话处理。

长期逻辑问题

账号、背包、任务等长期逻辑,以及客户端会话绑定,需要一个长期持有玩家逻辑对象的地方。于是有了:

BaseApp — 玩家在线会话的长期锚点。

实时空间问题

位置、AOI(Area of Interest)、移动、可见性、空间内实时交互,不适合放进同一套长期逻辑上下文里。于是有了:

CellApp — 实时空间运行引擎。

持久化问题

数据库应该负责冷状态、恢复状态和在线元信息,不应该直接变成实时世界的一部分。于是有了:

DBMgr(KBEngine)/ DBApp + DBAppMgr(BigWorld)— 在线/离线边界管理者。

要注意这两者不是严格的一一对应:BigWorld 把持久化执行层和集群协调层拆开,KBEngine 则合并成单个 DBMgr。

所以 Login / Base / Cell / DB 并不是拍脑袋拆服务,而是被四类问题自然塑造出来的分工。

2.4 为什么 Entity 一定会出现"多侧表示"

一旦接受了 Base / Cell 分离,立刻遇到一个问题:一个玩家到底算一个实体,还是两个实体?

BigWorld 一系给出的答案:

  • 语义上它仍然是"同一个实体"
  • 运行时上它有不同侧的表示(multi-representation)

于是就有了:

侧驻留进程职责
Base EntityBaseApp长期逻辑主体:背包、任务、社交、DB 交互
Cell EntityCellApp实时空间主体:位置、AOI、移动、战斗
Client 表现客户端网络表现:接收同步、发送操作

核心思想:一个游戏对象不是只能有一个物理驻留点。不同侧分别承载它的不同职责。

在 BigWorld 源码中,Cell 侧 Entity 有明确的 real/ghost 区分:

// 文件:programming/bigworld/server/cellapp/entity.hpp:138
/**
 *  Instances of this class are used to represent a generic game object on the
 *  cell. An entity may be real or ghosted. A ghost entity is an entity that
 *  is a copy of a real entity that lives on an adjacent cell.
 */
class Entity : public PyObjectPlus
{
    // ...
    bool isReal() const;
    bool isRealToScript() const;
    RealEntity * pReal() const;

    void initReal( BinaryIStream & data, ... );
    void initGhost( BinaryIStream & data );
    // ...
};

同一个 Entity 类,initReal 和 initGhost 是两条完全不同的初始化路径。

KBEngine 的 Cell 侧 Entity 也有同样的设计:

// 文件:kbe/src/server/cellapp/entity.h:46
class Entity : public script::ScriptObject
{
    // ...
    INLINE bool isReal(void) const;
    // ...
};

2.5 Proxy:把"玩家在线连接"带进实体模型

Proxy 不是普通实体,而是把"客户端会话"带进实体模型的关键对象。

它最重要的双重身份:

  • 它是一个 Entity(继承自 Base/Entity)
  • 它是客户端会话的入口

一个玩家不只是数据库里的一条记录,也不只是世界里的一个坐标点,他还是一个受客户端控制、可能重连、可能挤号、可能切换控制权的在线会话对象。Proxy 就是这层语义的集中承载点。

BigWorld Proxy

// 文件:programming/bigworld/server/baseapp/proxy.hpp:74
/**
 *  This class is used to represent a proxy. A proxy is a special type of base.
 *  It has an associated client.
 */
class Proxy: public Base
{
    // ...
    // 断线原因枚举
    enum ClientDisconnectReason
    {
        CLIENT_DISCONNECT_CLIENT_REQUESTED,
        CLIENT_DISCONNECT_GIVEN_TO_OTHER_PROXY,   // 挤号
        CLIENT_DISCONNECT_RATE_LIMITS_EXCEEDED,
        CLIENT_DISCONNECT_TIMEOUT,
        CLIENT_DISCONNECT_BASE_RESTORE,           // Base 恢复
        CLIENT_DISCONNECT_SHUTDOWN,
        CLIENT_DISCONNECT_CELL_RESTORE_FAILED,
    };
    // ...
};

注意 CLIENT_DISCONNECT_GIVEN_TO_OTHER_PROXY — 挤号是 Proxy 级别的语义,不是 Entity 级别的。

KBEngine Proxy

// 文件:kbe/src/server/baseapp/proxy.h:28
class Proxy : public Entity
{
    // ...
    bool sendToClient(const Network::MessageHandler& msgHandler, Network::Bundle* pBundle);
    bool sendToClient(Network::Bundle* pBundle, bool immediately = false);
    double getRoundTripTime() const;
    double getTimeSinceHeardFromClient() const;
    // ...
};

Proxy 持有 sendToClient 能力,普通 Entity 没有。在 BaseApp 上,只有 Proxy 才有客户端连接。

2.6 Space、Cell、AOI、Witness:为什么是核心而不是附属

普通服务端直觉里,很容易把"可见性"理解成一个附加模块。但在 BigWorld 一系里,它是核心问题之一。

原因:

  • 世界很大
  • 实体很多
  • 客户端不可能也不应该收到整个世界

系统必须回答:当前这个客户端到底该看到谁?

这就逼出了几层关键结构:

Space — 世界运行空间

一个逻辑上的"世界"或"场景"。同一个游戏可以有多个 Space(多个副本、多个地图)。

// 文件:programming/bigworld/server/cellapp/space.hpp:49
/**
 *  This class is used to represent a space.
 */
class Space : public TimerHandler, public GeometryMapper
{
    SpaceID id() const { return id_; }
    Cell * pCell() const { return pCell_; }
    // ...
};

KBEngine 用 SpaceMemory 做类似的事:

// 文件:kbe/src/server/cellapp/spacememory.h:20
class SpaceMemory
{
    void addEntity(Entity* pEntity);
    void addEntityAndEnterWorld(Entity* pEntity, bool isRestore = false);
    void removeEntity(Entity* pEntity);
    void onEntityAttachWitness(Entity* pEntity);
    // ...
};

Cell — 空间运行的最小承载单元

BigWorld 中,一个 Space 可以被分成多个 Cell(通过 BSP 树动态分割)。每个 Cell 承载一块区域内的 real entity。

// 文件:programming/bigworld/server/cellapp/cell.hpp:45
/**
 *  This class is used to represent a cell.
 */
class Cell
{
    class Entities
    {
        bool add( Entity * pEntity );
        bool remove( Entity * pEntity );
        // ...
    };
    // ...
};

KBEngine 的简化:没有 BSP 树动态分割,一个 SpaceMemory 对应一块空间。

AOI(Area of Interest)— 可见范围判定

决定"谁在附近"的机制。不是简单的距离检测,而是一整套空间索引 + 触发器系统。具体算法(十字链表、RangeTrigger)在 Ch14 详解。

Witness — 把 AOI 事件变成客户端同步

Witness 不是"方便一点的观察者类",而是大规模在线世界里客户端可见性控制的核心执行点。

// 文件:programming/bigworld/server/cellapp/witness.hpp:25
/**
 *  This class is a witness to the movements and perceptions of a RealEntity.
 *  It is created when a client is attached to this entity. Its main activity
 *  centres around the management of an Area of Interest list.
 */
class Witness : public Updatable
{
    RealEntity & real() { return real_; }
    // ...
};

关键点:Witness 是当客户端附着到实体时才创建的。不是每个实体都有 Witness,只有"被客户端控制"的实体才有。

KBEngine 同样:

// 文件:kbe/src/server/cellapp/witness.h:57
/**
 *  这个模块用来监视我们感兴趣的entity数据,如:view,属性更新,
 *  调用entity的方法并将其传输给监视者。
 */
class Witness : public PoolObject, public Updatable
{
    // ...
    struct WitnessInfo
    {
        int8 detailLevel;       // 当前所在详情级别
        Entity* entity;         // 所表达的entity
        float range;            // 当前与这个entity的距离
        bool detailLevelLog[3]; // 进入过哪些详情级别(属性广播优化)
        // ...
    };
    // ...
};

注意 detailLevel — 不是所有可见实体都同步全部属性,远处只同步位置,近处才同步全部。这是 Witness 级别的优化,不是网络层的。

2.7 Ghost:分布式空间协作的必要存在

一旦空间可以分布式运行(多个 CellApp),又会碰到一个问题:

某个实体真实逻辑在别的 Cell 上,但本地又需要知道它的存在和部分状态,怎么办?

Ghost 不是"复制一份实体玩玩",而是在分布式空间运行中,为远端真实实体提供局部可见和协作副本。

BigWorld 中,Entity 的 real/ghost 由 RealEntity 管理:

// 文件:programming/bigworld/server/cellapp/real_entity.hpp:46
/**
 *  An object of this type is used by Entity to store additional data
 *  when the entity is "real" (as opposed to ghosted).
 */
class RealEntity
{
    // ...
    class Haunt  // Ghost 驻留在哪个 CellAppChannel
    {
        CellAppChannel * pChannel_;
        GameTime creationTime_;
        // ...
    };
    // ...
};

一个 RealEntity 知道自己有哪些 Ghost(通过 Haunt 列表),Ghost 知道自己的 real 在哪(通过 realAddr)。

KBEngine 没有 BigWorld 那样完整展开成一套 RealEntity + Haunt 命名体系,但 real/ghost 仍然是明确的一等运行态,而不是文档层面的概念借用:

// 文件:kbe/src/server/cellapp/entity.cpp
KBE_ASSERT(isReal() == true && "Entity::changeToGhost(): not is real.\n");
// ...
KBE_ASSERT(isReal() == false && "Entity::changeToReal(): not is ghost.\n");

也就是说,KBEngine 同样把 ghost 视为 Cell 侧分布式空间协作的一部分,只是实现组织比 BigWorld 更收敛。

2.8 Mailbox vs EntityCall:远端实体的"邮箱地址"

BigWorld Mailbox

Mailbox 是远端实体的"邮箱地址"。持有 Mailbox 就能向那个实体发消息,不需要知道它在哪台机器上。

// 文件:programming/bigworld/server/cellapp/mailbox.hpp:30
/**
 *  This class implements a mailbox that can send to a server object.
 *  This object may be on a cell or may be a base.
 */
class ServerEntityMailBox: public PyEntityMailBox
{
    virtual const Mercury::Address address() const { return addr_; }
    virtual EntityID id() const { return id_; }
    virtual EntityMailBoxRef::Component component() const = 0;
    // ...
};

// 两种特化
class CellEntityMailBox: public ServerEntityMailBox { /* ... */ };
class BaseEntityMailBox: public ServerEntityMailBox { /* ... */ };

addr_ + id_ + component 三元组唯一确定一个远端实体。

KBEngine EntityCall

KBEngine 用 EntityCall 做同样的事,但命名不同:

// 文件:kbe/src/lib/entitydef/entity_call.h:15
class EntityCall : public EntityCallAbstract
{
    EntityCall(ScriptDefModule* pScriptModule, const Network::Address* pAddr,
        COMPONENT_ID componentID, ENTITY_ID eid, ENTITYCALL_TYPE type);
    // ...
    virtual void newCall(Network::Bundle& bundle);
    virtual RemoteEntityMethod* createRemoteMethod(MethodDescription* pMethodDescription);
    // ...
};

对应关系:

BigWorldKBEngine含义
MailboxEntityCall远端实体的引用
CellEntityMailBoxCellEntityCall指向 Cell 侧实体
BaseEntityMailBoxBaseEntityCall指向 Base 侧实体
ClientEntityMailBoxClientEntityCall指向客户端实体

这里只建立概念对应。到了 RPC 和 AOI 章节还要进一步区分:客户端这侧的方法调用会受到 Witness / 可见性约束,不能简单等同于普通的 Base/Cell 远程调用。

核心差异:BigWorld 有 TwoWay RPC(带返回值的 Mailbox 调用),KBEngine 只有单向 EntityCall + CallbackMgr。这在 Ch11 详述。

Actor vs RPC 心智模型(读源码时最容易混淆的点)

你的直觉是对的:BigWorld 在语义表达上更偏 Actor,KBEngine 在接口外观上更偏 RPC。

观察维度BigWorld(Mailbox)KBEngine(EntityCall)
第一心智给“远端实体地址”投递消息调用“远端实体方法”
默认调用感受消息投递(fire-and-forget)远程方法调用(仍是异步)
返回值语义TwoWay + Deferred 补充无内建返回,靠 CallbackMgr 旁路
地址抽象Mailbox 本身强调“收件箱地址”EntityCall 更强调“方法入口”

但本质上两者都不是“纯 Actor”或“纯 RPC”,而是有状态分布式实体消息系统:

  1. 调用目标是实体实例(有 ID、有驻留侧),不是无状态服务
  2. 传输层以异步消息为主,不是本地函数语义
  3. Base/Cell/Client 执行域是路由模型的一部分,不是实现细节

实践上建议统一团队心智:把它当“异步消息投递 + 可选回复通道”,不要当成本地函数调用。

TwoWay 是什么(在 2.8 先建立直觉)

TwoWay 可以理解为:带回复通道的远程调用。
它不是同步阻塞,而是“先发请求,再异步收到结果或错误”。

和 OneWay 的区别:

模式调用后拿到什么是否等待结果常见用途
OneWay无返回句柄否高频状态同步、普通实体消息
TwoWay一个 Deferred(未来结果)异步等待需要结果的查询/校验链路

BigWorld 脚本层示意:

d = someEntity.someMethod.twoWay(args)
d.addCallback(on_ok)
d.addErrback(on_err)

典型流程:

  1. 发起调用时创建请求跟踪项(replyID / handler / timeout)
  2. 远端处理完成后回包(成功值或错误)
  3. 收到回包后命中请求跟踪项
  4. 触发 Deferred 的 callback 或 errback

对照 KBEngine:没有内建 TwoWay,通常是“单向 EntityCall + callbackID(CallbackMgr)”来模拟一次请求一次响应。

2.9 统一运行模型

把前面的对象放回一张统一模型图:

客户端
  │
  │ TCP/KCP
  ▼
LoginApp ──── 认证 ────▶ DBMgr(查账号)
  │
  │ 分配 BaseApp
  ▼
BaseApp
  ├── Proxy(客户端会话锚点)
  ├── Base Entity(长期逻辑:背包/任务/社交)
  │     │
  │     │ 组件消息(创建 Cell Entity)
  │     ▼
  └──▶ CellApp
        ├── Cell Entity(实时空间:位置/移动/战斗)
        ├── Space(世界空间)
        ├── Witness(AOI → 客户端同步)
        ├── Ghost(跨 Cell 可见性副本)
        │
        │ 必要时向 Base 收束空间态
        ▼
      Base / DBMgr(持久化入口)

八步主线:

  1. 客户端通过 LoginApp 接入
  2. 玩家长期逻辑实体停在 BaseApp 的 Base Entity
  3. 进入世界后在 CellApp 创建 Cell Entity
  4. 若该玩家受客户端控制,则通过 Proxy 绑定连接
  5. 所在 Space 决定它处于哪个世界上下文
  6. Witness 负责把周围实体的可见性变化同步给客户端
  7. 若实体跨 Cell 运行,Ghost 帮助维持分布式空间协作
  8. DBMgr 负责持久化、恢复和在线元信息

这些术语并不是散落的定义,而是同一个系统模型里的不同支点。

2.10 为什么这套模型今天看起来仍然成立

即使技术栈变了,BigWorld 一系模型仍然有生命力:

问题2002 年成立2026 年仍然成立
大规模在线状态需要分层✓✓
实时空间和长期业务不同✓✓
客户端可见性不能靠全量广播✓✓
持久化不应直接充当实时状态主源✓✓
单进程无法承载整个世界✓✓

KBEngine 继续沿用这套模型,不是历史包袱,而是这些问题本身还没消失。

对比今天的微服务架构:

  • 微服务解决的是"无状态请求的水平扩展"
  • BigWorld/KBEngine 解决的是"有状态实体的分布式运行"
  • 两者面对的问题域不同,不能用同一套方案

2.11 关键源码入口

BigWorld

概念文件路径
Base 实体server/baseapp/base.hpp
Proxyserver/baseapp/proxy.hpp
Cell Entityserver/cellapp/entity.hpp
Real/Ghostserver/cellapp/real_entity.hpp
Spaceserver/cellapp/space.hpp
Cellserver/cellapp/cell.hpp
Witnessserver/cellapp/witness.hpp
Mailboxserver/cellapp/mailbox.hpp
LoginAppserver/loginapp/loginapp.hpp
DBAppserver/dbapp/dbapp.hpp

KBEngine

概念文件路径
Base Entitykbe/src/server/baseapp/entity.h
Proxykbe/src/server/baseapp/proxy.h
Cell Entitykbe/src/server/cellapp/entity.h
SpaceMemorykbe/src/server/cellapp/spacememory.h
Witnesskbe/src/server/cellapp/witness.h
EntityCallkbe/src/lib/entitydef/entity_call.h
LoginAppkbe/src/server/loginapp/loginapp.h
DBMgrkbe/src/server/dbmgr/dbmgr.h

2.12 源码走读路径

建议按以下顺序在 IDE 中打开文件:

路径一:理解 Entity 的多侧表示

  1. BigWorld: server/cellapp/entity.hpp — 看类声明,关注 isReal() / initReal() / initGhost()
  2. BigWorld: server/cellapp/real_entity.hpp — 看 RealEntity 的 Haunt 列表
  3. KBEngine: kbe/src/server/cellapp/entity.h — 对比 isReal() 和 real/ghost 判定
  4. KBEngine: kbe/src/server/baseapp/entity.h — 看 Base 侧 Entity 的 DB 相关方法

路径二:理解 Proxy 的双重身份

  1. BigWorld: server/baseapp/base.hpp — 先看 Base 的基础能力
  2. BigWorld: server/baseapp/proxy.hpp — 看 Proxy 继承 Base 后加了什么(客户端连接管理)
  3. KBEngine: kbe/src/server/baseapp/proxy.h — 对比 sendToClient 方法族

路径三:理解可见性控制链

  1. BigWorld: server/cellapp/space.hpp — Space 持有 Cell
  2. BigWorld: server/cellapp/cell.hpp — Cell 持有 Entities
  3. BigWorld: server/cellapp/witness.hpp — Witness 绑定到 RealEntity
  4. KBEngine: kbe/src/server/cellapp/witness.h — 看 WitnessInfo 的 detailLevel

2.13 小结

这一章最重要的收获:

  • BigWorld 模型首先是问题驱动的,不是术语驱动的
  • Login / Base / Cell / DB 是四类不同问题的自然分工
  • 实体必须允许多侧表示,才能支撑长期逻辑和实时空间分离
  • Proxy 是实体模型里"在线会话"语义的承载点,不是普通 Entity
  • Witness 不是附加功能,而是大规模在线世界客户端可见性控制的核心执行点
  • Ghost 是分布式空间协作的必要机制,不是可选的缓存策略
  • Mailbox/EntityCall 是远端实体的引用句柄,持有它就能通信,无需知道物理位置

把这层心智模型建立起来之后,再去读 KBEngine 的整体架构和源码实现,很多设计就不会显得突兀。

Prev
1. 导读与阅读方法
Next
3. KBEngine 系统全景