Xmldef Phase 1 Implementation Plan
Status: draft / implementation-phase spec.
本文把
xmldef的设计草案继续收敛为 Phase 1 可开工范围,冻结 compiler MVP、descriptor.debug.json固定格式、runtimeDescriptorRegistry最小执行面,以及XmldefBodyCodec第一版边界。目标不是覆盖完整 schema 系统,而是定义一个最小、干净、可逐步扩展的第一阶段交付面。如果关注“代码应该怎么拆、哪些模块属于 toolchain、哪些模块属于 runtime/transport/Lua”,请结合 Xmldef Compiler / Runtime MVP 一起阅读。
Phase 1 Goal
Phase 1 的目标不是完成完整 KBEngine/BigWorld 风格 schema runtime,而是先打通这条最小闭环:
xmldef xml
-> compiler MVP
-> descriptor.debug.json + route_constants.json
-> runtime DescriptorRegistry
-> XmldefBodyCodec MVP
-> Lua binding registry MVP交付完成后,应满足:
- 服务端不再直接依赖 XML 热路径解析
- runtime 能从 descriptor 读到
route_id -> method schema xmldef能作为真实BodyCodec的第一版执行面存在- Unity/U3D generator 已有稳定输入格式,即使生成器本身还未完全实现
Out Of Scope
Phase 1 明确不做:
descriptor.bin二进制格式冻结- 多版本 descriptor patch / Merkle 增量
- 完整 call/stream schema runtime
- Unity 网络 SDK 实现
- Unreal / TS generator
- XML mapper / entity runtime
- 高级 validation 规则执行
Phase 1 先冻结 JSON debug descriptor 作为稳定输入输出契约。
Deliverables
Phase 1 必须交付四块:
xmldefcompiler MVPdescriptor.debug.json固定格式- runtime
DescriptorRegistryMVP - runtime
XmldefBodyCodec + Lua binding registryMVP
这里的命名是 Phase 1 落地口径,不代表 xmldef 在架构上必须等于单一 body wire format。长期上,xmldef 更像 contract source / descriptor provider;Phase 1 只是先把它通过一个 runtime codec 适配进固定 pipeline。
Compiler MVP
Input
compiler MVP 输入:
protocol/manifest.xml- referenced module XML files
Output
compiler MVP 最小输出:
descriptor.debug.jsonroute_constants.jsonmanifest.json
Phase 1 可以先不产出最终 descriptor.bin,或者只保留空壳占位。
真正稳定的 runtime/generator 契约,Phase 1 以 descriptor.debug.json 为准。
Required Features
compiler MVP 必须支持:
- manifest 读取
- module 加载
message/method收集- type graph 收集
- semantic validation
- canonical ordering
- JSON descriptor 输出
Required Semantic Validation
Phase 1 至少要拦住这些错误:
- 重复
route_id - 重复 method full name
- request/response type 缺失
- struct 字段 ID 冲突
- struct 字段名冲突
- enum value 冲突
- enum name 冲突
- 非法
direction - 非法
kind - client-visible 类型引用 server-only 类型
Deferred Validation
以下校验留到后续 phase:
- schema compatibility diff
- Merkle subtree diff
- 高级 validation rule 执行
- 复杂 binding hint 策略
- entity / mapper 交叉检查
descriptor.debug.json Format
Why JSON First
Phase 1 先冻结 descriptor.debug.json,理由很直接:
- generator plugin 更容易接入
- runtime prototype 更容易调试
- QA / 工具更容易检查
- 可以先把 compiler/runtime/generator 解耦
二进制 descriptor.bin 可以在 Phase 2 再严格收口。
Top-level Shape
固定顶层结构如下:
{
"format": "shield.xmldef.descriptor.debug.v1",
"package": { ... },
"modules": [ ... ],
"types": [ ... ],
"methods": [ ... ],
"routes": [ ... ]
}format 是强制字段,用于 generator/runtime 做版本检查。
package
固定字段:
{
"id": "game",
"name": "game",
"version": "1.0.0",
"code_namespace": "Game.Protocol",
"compiler_version": "0.1.0",
"schema_root_hash": "hex-string"
}modules
每个 module:
{
"module_id": 1,
"name": "avatar",
"path": "avatar",
"dependencies": [0],
"hash": "hex-string"
}types
每个 type 最小字段:
{
"type_id": 1001,
"module_id": 1,
"full_name": "avatar.MoveRequest",
"kind": "struct",
"client_visibility": "client",
"hash": "hex-string",
"struct": {
"fields": [ ... ],
"reserved_field_ids": []
}
}enum 示例:
{
"type_id": 1002,
"module_id": 1,
"full_name": "avatar.MoveMode",
"kind": "enum",
"client_visibility": "client",
"hash": "hex-string",
"enum": {
"values": [
{ "name": "Walk", "value": 0 },
{ "name": "Run", "value": 1 }
]
}
}type_ref
字段引用固定为显式对象,而不是字符串缩写:
{
"kind": "named",
"named_type_id": 1001
}list:
{
"kind": "list",
"item": {
"kind": "named",
"named_type_id": 1001
}
}map:
{
"kind": "map",
"key": { "kind": "scalar", "scalar_kind": "string" },
"value": { "kind": "named", "named_type_id": 1001 }
}methods
每个 method 固定字段:
{
"method_id": 1,
"route_id": 4097,
"module_id": 1,
"full_name": "Avatar.move",
"short_name": "move",
"kind": "send",
"direction": "client_to_server",
"request_type_id": 1001,
"response_type_id": null,
"item_type_id": null,
"target_hint": null,
"binding_hint": "on_avatar_move",
"hash": "hex-string"
}routes
必须保留显式 route 表:
{
"route_id": 4097,
"method_id": 1,
"full_name": "Avatar.move",
"direction": "client_to_server",
"schema_name": "Avatar.move"
}schema_name 直传 host RouteEntry.schema_name(显式 request_schema 覆盖优先,否则 full_name 同名约定)。收敛后的 codec ABI 只见这个 名字;descriptor 不向管线暴露 schema_id 一类的数字寻址,那类编号 只是具体 schema 系统的内部概念。
route_constants.json
Phase 1 同时输出简化 route 表:
{
"format": "shield.xmldef.route_constants.v1",
"package": "game",
"version": "1.0.0",
"routes": [
{
"route_id": 4097,
"name": "Avatar.move",
"schema_name": "Avatar.move",
"direction": "client_to_server"
},
{
"route_id": 4098,
"name": "Login.reqAuth",
"schema_name": "Login.reqAuth",
"direction": "client_to_server"
}
]
}End-to-end Example
Source Sketch
下面给一份最小但完整的示例,帮助后续 compiler/runtime/generator 对齐语义:
<protocol-manifest name="game"
version="1.0.0"
packageId="game"
codeNamespace="Game.Protocol">
<module name="common" path="common"/>
<module name="login" path="login"/>
<module name="avatar" path="avatar"/>
</protocol-manifest>假定 XML 语义里包含:
common.Vector3login.AuthRequestlogin.CharacterSummarylogin.AuthReplyavatar.MoveModeavatar.MoveRequestLogin.reqAuth(call,client_to_server)Avatar.move(send,client_to_server)
Example descriptor.debug.json
下面这份 JSON 应当可以直接作为 generator、QA 工具和 runtime loader 的对照样例:
{
"format": "shield.xmldef.descriptor.debug.v1",
"package": {
"id": "game",
"name": "game",
"version": "1.0.0",
"code_namespace": "Game.Protocol",
"compiler_version": "0.1.0",
"schema_root_hash": "b4556a5e4e8a6e3a90d3c55df4b95a2cf4f06243f4f4a6b0db8b28331ad0b6ff"
},
"modules": [
{
"module_id": 1,
"name": "common",
"path": "common",
"dependencies": [],
"hash": "fd148f46a6db2722ea6bd1db0f1bd1af2f2324d35f0b4d6a466095a84e4a7771"
},
{
"module_id": 2,
"name": "login",
"path": "login",
"dependencies": [1],
"hash": "6a17d7f6de4ea0cef5dd7b31d7c91f6fa0f0682f565f6f83b5fcdf9ccaa3d70c"
},
{
"module_id": 3,
"name": "avatar",
"path": "avatar",
"dependencies": [1],
"hash": "22ac0710f57aeb1b09f4e8f8a6dbbe0a0d0a7c58e81b080c41d0c9db64167c53"
}
],
"types": [
{
"type_id": 1001,
"module_id": 1,
"full_name": "common.Vector3",
"kind": "struct",
"client_visibility": "client",
"hash": "eb401d58db8a4fc0133bcf396f3e3ddc0f7ba0beee0cb9f7bf8d972f22f4fd01",
"struct": {
"fields": [
{
"field_id": 1,
"name": "x",
"type_ref": { "kind": "scalar", "scalar_kind": "float" },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
},
{
"field_id": 2,
"name": "y",
"type_ref": { "kind": "scalar", "scalar_kind": "float" },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
},
{
"field_id": 3,
"name": "z",
"type_ref": { "kind": "scalar", "scalar_kind": "float" },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
}
],
"reserved_field_ids": []
}
},
{
"type_id": 2001,
"module_id": 2,
"full_name": "login.AuthRequest",
"kind": "struct",
"client_visibility": "client",
"hash": "f0a4a613547f1df40cdf1f6bfb8e17d5f0118028470d4db6fd311d223eef09c2",
"struct": {
"fields": [
{
"field_id": 1,
"name": "account",
"type_ref": { "kind": "scalar", "scalar_kind": "string" },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
},
{
"field_id": 2,
"name": "token",
"type_ref": { "kind": "scalar", "scalar_kind": "string" },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
}
],
"reserved_field_ids": []
}
},
{
"type_id": 2002,
"module_id": 2,
"full_name": "login.CharacterSummary",
"kind": "struct",
"client_visibility": "client",
"hash": "32b0ff4418a4fb4c28329cae05f0946e8dff0a56da94290435603f49102cf244",
"struct": {
"fields": [
{
"field_id": 1,
"name": "player_id",
"type_ref": { "kind": "scalar", "scalar_kind": "uint64" },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
},
{
"field_id": 2,
"name": "nickname",
"type_ref": { "kind": "scalar", "scalar_kind": "string" },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
},
{
"field_id": 3,
"name": "level",
"type_ref": { "kind": "scalar", "scalar_kind": "uint32" },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
}
],
"reserved_field_ids": []
}
},
{
"type_id": 2003,
"module_id": 2,
"full_name": "login.AuthReply",
"kind": "struct",
"client_visibility": "client",
"hash": "5e31d9ee12f1fd04f2c5962e8fd893fe6ef61dcb5e84b154f6effe9ee753ccf0",
"struct": {
"fields": [
{
"field_id": 1,
"name": "session_token",
"type_ref": { "kind": "scalar", "scalar_kind": "string" },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
},
{
"field_id": 2,
"name": "characters",
"type_ref": {
"kind": "list",
"item": {
"kind": "named",
"named_type_id": 2002
}
},
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
}
],
"reserved_field_ids": []
}
},
{
"type_id": 3001,
"module_id": 3,
"full_name": "avatar.MoveMode",
"kind": "enum",
"client_visibility": "client",
"hash": "00fa7f6a0c3d2bd6dbb7ec8a53d6bcb93fd46fa38642ad45653022fb7ee65f90",
"enum": {
"values": [
{ "name": "Walk", "value": 0 },
{ "name": "Run", "value": 1 }
]
}
},
{
"type_id": 3002,
"module_id": 3,
"full_name": "avatar.MoveRequest",
"kind": "struct",
"client_visibility": "client",
"hash": "f2b94ed7bbd11a64d7cf45cb975484617b1b5b0af27960486cf2ba1139de9ff0",
"struct": {
"fields": [
{
"field_id": 1,
"name": "entity_id",
"type_ref": { "kind": "scalar", "scalar_kind": "uint64" },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
},
{
"field_id": 2,
"name": "position",
"type_ref": { "kind": "named", "named_type_id": 1001 },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
},
{
"field_id": 3,
"name": "mode",
"type_ref": { "kind": "named", "named_type_id": 3001 },
"cardinality": "optional",
"default_value": null,
"deprecated": false,
"validation_rules": []
}
],
"reserved_field_ids": []
}
}
],
"methods": [
{
"method_id": 1,
"route_id": 4098,
"module_id": 2,
"full_name": "Login.reqAuth",
"short_name": "reqAuth",
"kind": "call",
"direction": "client_to_server",
"request_type_id": 2001,
"response_type_id": 2003,
"item_type_id": null,
"target_hint": "auth",
"binding_hint": "on_login_req_auth",
"hash": "265c5ffbc0bce4a727cd95df95f5560af0a96485d8d89ba89723d2be69f02efe"
},
{
"method_id": 2,
"route_id": 4097,
"module_id": 3,
"full_name": "Avatar.move",
"short_name": "move",
"kind": "send",
"direction": "client_to_server",
"request_type_id": 3002,
"response_type_id": null,
"item_type_id": null,
"target_hint": "avatar",
"binding_hint": "on_avatar_move",
"hash": "39a31598f6ca21141fb1b85a2faf2bf532cab4ce6289fdcb75ebf70e1ac054c4"
}
],
"routes": [
{
"route_id": 4097,
"method_id": 2,
"full_name": "Avatar.move",
"direction": "client_to_server",
"schema_name": "Avatar.move"
},
{
"route_id": 4098,
"method_id": 1,
"full_name": "Login.reqAuth",
"direction": "client_to_server",
"schema_name": "Login.reqAuth"
}
]
}这个例子刻意覆盖了:
structenum- named type reference
list<T>sendcall- request/response 对
- route table 和 method table 的双重显式表达
Runtime DescriptorRegistry MVP
Responsibility
Phase 1 的 DescriptorRegistry 只做 descriptor 查询,不做:
- 兼容性检查
- patch 合并
- 多版本共存
- 复杂 auth policy 执行
Required Queries
Phase 1 至少实现:
find_method_by_route(route_id)find_method_by_name(full_name)find_type(type_id)find_request_type(route_id)find_response_type(route_id)find_binding_hint(route_id)
Load Source
Phase 1 runtime 直接加载 descriptor.debug.json 即可。
等 binary descriptor 稳定后,再切到 descriptor.bin + debug json 辅助调试。
这意味着 Phase 1 runtime 不需要等 binary package 先落地。
XmldefBodyCodec MVP
Responsibility
Phase 1 的 XmldefBodyCodec 只负责:
route_id -> method schema- request/response type lookup
- minimal typed field encode/decode
DecodedBody.message产出
Phase 1 Supported Types
第一版建议只支持:
boolint32/int64uint32/uint64float/doublestringbytesenumstruct
以及:
list<T>
先不支持:
map<K,V>- alias-specific special handling
- entity
- union / oneof / variant
如果需要,可以把 map 延到 Phase 1.5。
Egress Rule
XmldefBodyCodec.encode 必须接受“业务消息对象”,不能接受未建模 raw bytes 旁路。
这和当前 json/msgpack structured codec 的出站边界保持一致。
Decode Output Shape
Phase 1 decode 后给 Lua 的 DecodedBody.message 形状统一为 object:
{
"__xmldef_method": "Avatar.move",
"__xmldef_route_id": 4097,
"__xmldef_schema_name": "Avatar.move",
"entity_id": 7,
"x": 10,
"y": 20,
"z": 30
}是否保留这些元字段进 Lua,可以在后续 phase 再决定。
但 runtime 内部必须保留 method metadata,不能只剩字段表。
Lua Binding Registry MVP
Input
Phase 1 接受显式 binding table:
M.xmldef = {
bindings = {
["Avatar.move"] = "on_avatar_move"
}
}Compile Step
启动时做一次:
descriptor methods
+ lua binding table
-> route_id -> cached functionRequired Validation
Phase 1 必须拦住:
- descriptor 有 method,但 binding 缺失
- binding 指向不存在函数
- 两个 method 映射到同一不兼容 handler 名的非法配置
Runtime Dispatch
Phase 1 runtime dispatch 不做动态字符串查找,只允许:
route_id -> cached Lua functionCompiler/Runtime Boundary
必须保持这条边界:
- compiler 负责把 XML 解释成 descriptor
- runtime 只负责执行 descriptor
runtime 不允许:
- 直接重新读 XML
- 在热路径做 XML 语义解释
- 猜测 route / field / binding
Recommended Module Split
Phase 1 推荐明确拆成四层,而不是把所有逻辑塞进一个 XmldefBodyCodec:
- compiler:负责
xml -> canonical IR -> descriptor.debug.json - descriptor runtime:负责 descriptor load、索引和 schema 查询
- transport adapter:负责把 runtime codec 接到固定 protocol pipeline
- Lua binding compiler:负责启动期校验和
route_id -> cached lua function
更细的模块职责、建议源码布局和启动流程见 Xmldef Compiler / Runtime MVP。
Unity/U3D Generator Dependency
Phase 1 虽然可以暂时还不实现完整 Unity generator,但 descriptor.debug.json 必须已经稳定到足以支持:
- 生成
RouteIds.cs - 生成 DTO
- 生成
GatewayClient.cs
也就是说,compiler Phase 1 的输出不是“只够服务端自用”,而是“已经够前端生成器接入”。
Recommended Task Breakdown
建议按下面顺序开工:
- XML reader + semantic validator
- canonical IR builder
descriptor.debug.jsonwriterroute_constants.jsonwriter- runtime JSON descriptor loader
DescriptorRegistryquery APIXmldefBodyCodecsupport for scalar/enum/struct/list- Lua binding registry compile + validation
Done Criteria
Phase 1 可视为完成,当且仅当:
- 给定一组
xmldef/xml,能稳定产出descriptor.debug.json - runtime 能从该 descriptor 完成
route_id -> schema查询 XmldefBodyCodec能完成至少一个真实 struct message 的 encode/decode- Lua service 能通过显式 binding 表接收该 message
- Unity generator 所需字段已经全部能从 descriptor.debug.json 读取
Non-goals
以下不属于 Phase 1 完成标准:
- binary descriptor 稳定格式
- 热更新 patch
- 完整 client runtime
- Unreal / Web generator
- XML mapper runtime