一句话看懂
REDox 是 CAPCOM 下一代游戏引擎 REX 的核心数据引擎,用 64 位固定尺寸 token 表示结构化数据,反序列化 canada.json 分配 2.56 MB 内存(System.Text.Json 需 8.53 MB),顺序反序列化快 1.8 倍,支持 JSON/CBOR/MessagePack/TOML/XML 格式互转,提供可编辑 token DOM 操作容器增删改无需重建完整树。
它解决什么问题
内存占用高:传统 JSON DOM 为每个节点分配对象,REDox 用固定 64 位 token 存储类型和 payload(数据载荷),未修改的值通过源缓冲区偏移延迟解码,canada.json 分配减少 70%。
格式转换低效:提供格式无关的 DataReader/DataWriter 抽象,token IR(中间表示)作为桥梁可直接在 JSON token DOM 上调用 CborDocument.Encode 转换为 CBOR 字节流。
编辑破坏原始格式:通过可变 tape DOM 设计,容器视图的值槽重链和空闲列表实现插入删除,JSON5 编辑时保留 trivia(注释、空白)。
核心概念速览
token DOM / IR:将结构化数据解析为固定大小 token 的中间表示。REDox 用 64 位 token 同时表示源数据视图和可编辑 DOM,相比传统 node DOM 节省分配,相比 tape DOM 增加随机访问能力。
dual-mode token(双模式 token):extension bit 为 0 时 payload 由文档层解释(如 JSON 视为 UTF-8 源缓冲区偏移和长度),为 1 时由 REDox 固定解释(如存储堆分配字符串指针或修改后的容器节点 ID),通过位域复用单个 64 位结构实现零开销抽象。
asymmetric read/write(非对称读写):序列化时已知值类型可直接写入缓冲区(DataWriter 单次遍历),反序列化时先建 token DOM 再随机访问(DataReader 支持预分配、乱序绑定构造函数参数、引用解析 $ref),用不对称换取读性能。
lazy decoding(延迟解码):token 存储源偏移和长度,字符串、数字、时间戳等仅在请求时解码(如调用 ReadString 才执行 UTF-8 到 string 转换),未修改值复用原始切片避免分配。
mutable tape DOM(可变 tape DOM):传统 tape DOM 用连续数组存储节点难以编辑,REDox 通过容器视图值槽链表和空闲列表实现插入删除替换,空闲槽复用避免数组重分配,编辑后的 token 标记 extension bit = 1 存储新值。
架构拆解
核心数据流:
JSON 字节流 → JsonDocument.Parse → DToken[] + 扩展信息 → DataReader 包装
↓
converter.Read(DataReader, tokenId) → Document.DecodeString/DecodeInteger
↓
对象实例 ← 反射或 source-generator 绑定属性
DToken:64 位 token 核心结构,位布局编码类型(前 8 位)、payload(中间 48 位)、扩展信息(extension bit)、容器计数、链接 ID 等。代码定义 MaxTokenId = 0x3fffffff 和 PayloadMask = 0x00FFFFFFFFFFFFFFL,限制单文档最多约 10 亿 token。
Document:管理 DToken 数组 _tokens 和扩展信息存储,提供 GetToken/SetToken 访问,子类(JsonDocument/CborDocument)实现抽象解码方法 DecodeUtf8Bytes/DecodeInteger/DecodeFloat,将 token payload 转换为具体值。版本控制字段 _version 跟踪编辑操作,编辑时递增版本使迭代器失效。
DataReader:格式无关反序列化读取器,包装 DElement(document + tokenId),通过 ReadBoolean/ReadString/ReadValue/EnumerateMap 统一接口访问 token。converter.Read 调用 DataReader 方法,DataReader 转发到 Document 解码实现,实现格式无关反序列化。
DataWriter:格式无关序列化写入器抽象基类,定义 WriteValue/WriteString/WriteArray 接口,子类(如 JsonWriter)实现具体格式输出到缓冲区。
DataConverter:类型转换器,实现 Read(DataReader, tokenId, existingValue) 和 Write(DataWriter, value),通过 Document.Settings.GetRootConverter 获取并复用。
JsonSerializer.Deserialize 先调用 JsonDocument.Parse 构建 token DOM,创建 DataReader 包装 RootElement,调用 converter.Read 读取 token 并绑定到对象。编辑操作通过 AsObject().Add(key, value) 修改容器视图,更新 token 链接 ID 和 extension bit,ToJsonString 重新遍历 token 输出。跨格式转换通过共享 token IR 实现,CborDocument.Encode(jsonDoc.RootElement) 读取 JSON token 用 CBOR writer 重新编码。
关键实现走读
DToken 位布局和构造
public partial struct DToken : IEquatable<DToken>
{
private const uint MaxTokenId = 0x3fffffff;
private const uint MaxValueCount = 0x3fffffff;
private const uint MaxExtendId = 0xfffffff;
private const long PayloadMask = 0x00FFFFFFFFFFFFFFL;
private const long InlineFloatMask = 0x07FFFFFFFFFFFFFFL;
internal DToken(long v)
{
_value = v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DToken MakeJump(uint jumpId)
{
Debug.Assert(jumpId <= MaxTokenId);
return new DToken(jumpId);
}
定义 64 位 token 位掩码常量和构造方法,MaxTokenId 限制单文档 token 数量,PayloadMask 提取低 56 位 payload 数据,MakeJump 创建跳转 token(容器结束标记用于快速跳过)。64 位对齐在现代 CPU 单次内存访问读取,payload 56 位足够存储 48 位偏移 + 8 位长度(字符串切片)或 30 位 token ID + 30 位计数(容器)。
DataReader 格式无关读取
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T? ReadValue<T>(uint tokenId, T? existingValue)
{
var converter = Document.Settings.GetRootConverter<T>();
if (converter is DataConverter<T> conv)
{
return conv.Read(this, tokenId, existingValue);
}
return (T?)converter.ReadObject(in this, typeof(T), tokenId, existingValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public object? ReadObject(uint tokenId, Type outputType, object? existingValue)
{
return Document.Settings.GetConverter(outputType).ReadObject(in this, outputType, tokenId, existingValue);
}
根据类型参数 T 获取对应 converter,调用 converter.Read 传入 DataReader 自身和 tokenId,converter 通过 DataReader 的 ReadString/ReadBoolean 方法访问 token,Document 子类解码方法从源缓冲区提取值。将格式解析和类型转换分离,converter 只关心读取抽象数据类型,Document 子类负责格式具体编码。
动手上手
安装核心包和格式扩展:
dotnet add package CAPCOM.REDox
dotnet add package CAPCOM.REDox.Cbor
dotnet add package CAPCOM.REDox.MessagePack
反序列化 JSON 到对象:
编辑 JSON5 保留注释:
跨格式转换 JSON 到 CBOR:
应用场景
游戏资产管道批量加载:使用 JsonSerializer.Deserialize<Item[]> 加载千级道具数据,启用 ParallelDeserializeEnabled 选项自动检测大型数组并行反序列化,利用多核将加载时间从 15 秒降到 5 秒。
配置工具编辑 JSON5:解析用户配置文件(包含注释说明各字段用途),通过 AsObject().Add/Remove 修改键值对,ToJson5String 重新编码保留原始注释和缩进。
微服务数据格式互转:前端传 JSON 请求,网关解析为 token DOM 后直接调用 MessagePackDocument.Encode 转换为 MessagePack 发送到后端服务,单次中间表示转换避免 JSON → 对象 → MessagePack 双重开销。
日志分析大规模解析:数 GB JSON 日志文件按行解析为 token DOM,延迟解码仅提取需要分析的字段(如时间戳、错误码),未访问字段保持源缓冲区切片不分配字符串。
独立分析
REDox 性能优势来自 token DOM 结构而非算法黑魔法。System.Text.Json 用链表式 JsonElement 存储节点,每节点包含类型标签、数据联合体、父子指针,分配开销线性于节点数。REDox 用固定 64 位 token 数组存储,节点间通过 token ID 索引而非指针,CPU 缓存友好且可预分配数组避免扩容。延迟解码配合源缓冲区切片,未修改的字符串和数字直接复用原始字节,减少 GC 压力。
canada.json 基准测试 REDox 分配 2.56 MB 对比 System.Text.Json 8.53 MB,符合理论预期(token 数组约 2 MB,扩展信息和容器元数据约 0.5 MB,System.Text.Json 每节点 40+ 字节加字符串重复分配)。序列化仅快 1.6 倍而非反序列化的 1.8 倍,因为序列化需遍历对象属性通过反射或 source-generator 获取值,token DOM 优势主要在解析阶段。
跨格式转换效率取决于格式语义差异。JSON 到 CBOR 是类型兼容投影(JSON number → CBOR int/float),token IR 可直接映射。JSON 到 XML 涉及结构冲突(JSON 数组无标签、XML 需命名子元素),需用户定义映射规则或接受投影损失。
并行反序列化依赖 token 结构的随机访问性。REDox 先扫描源缓冲区建立 token 边界(识别字符串引号、括号配对),将大型数组的子对象分段并行解析,每段独立写入 token 数组的预分配区域。这要求数据无引用依赖($ref 跨段引用需同步)。
对比 System.Text.Json,REDox 提供可编辑 token DOM 和跨格式转换是额外能力维度。System.Text.Json 的 JsonDocument 是只读 DOM,修改需反序列化为对象、修改后重新序列化。REDox 通过容器视图值槽链表实现原地编辑,编辑后的 token 标记 extension bit = 1 存储堆分配数据,未修改部分保持源切片。
局限与风险
TOML/XML/HTML/CSV 为预览组件,功能完整性未验证。这些格式可能缺少完整规范支持或存在边界情况 bug。XML 的命名空间、DTD、实体引用等复杂特性能否正确映射到 token DOM 未知。
token 位布局限制单文档规模。MaxTokenId = 0x3fffffff 约 10 亿 token,按平均每节点 2 个 token 估算支持 5 亿节点。数 GB JSON 文档可能超出限制,需分块解析或改用流式处理。
性能数据基于特定数据集不普遍适用。canada.json 是地理坐标数组,数值密集且重复性高,token DOM 的延迟解码和缓存友好性优势明显。嵌套深度大但节点少的 JSON 可能因随机访问 token 数组导致缓存未命中。
跨格式转换可能丢失语义。JSON 的 null 和 MessagePack 的 nil 语义一致,但 JSON 无二进制类型(MessagePack 的 bin)、无扩展类型(MessagePack 的 ext),转换时需映射为字符串或自定义结构,反向转换无法恢复原始类型。
AOT 和 source-generator 支持状态不明。材料提及 'foundation for future AOT / source-generator support' 说明当前依赖反射,NativeAOT 场景可能需要手动注册类型或等待官方 source-generator。
结论卡片
REDox 通过 token DOM 中间表示在内存占用和可编辑性上超越 System.Text.Json,适合 .NET 开发者需要高性能反序列化、跨格式数据转换或保留原始格式编辑的场景。游戏引擎资产管道、微服务数据交换、配置管理工具可直接受益于其设计。
不适合不依赖 .NET 运行时的项目,需要 TOML/XML/HTML/CSV 完整规范支持的场景应等待这些格式稳定后再采用。对单文档 token 数量(10 亿限制)敏感的超大规模数据处理需评估分块方案。当前缺少 source-generator 的 AOT 场景应优先考虑 System.Text.Json。
CAPCOM 将其作为下一代游戏引擎技术组件开源,说明设计经过生产验证且有持续维护预期。Apache-2.0 许可证降低商业使用门槛,适合需要兼顾速度、内存和编辑能力的 .NET 结构化数据处理。
REDox 以固定 token 换取更低内存与可编辑 DOM,为 .NET 高性能解析和跨格式转换提供新选择;适合游戏资产管道、微服务网关与日志分析,但需留意预览格式与 AOT 限制。