不稳定的csb

Author Avatar
Kanglai Qian 6月 21, 2025

昨天帮春哥看了个奇怪的问题: UI工程在没有发生修改的情况下,输出的二进制csb文件发生了变化。先用之前写的csb2json工具处理了下变化前后的csb,发现dump出来的数据是一模一样的。

这块逻辑最早来自cocos2dx, csb本质其实是flatbuffers序列化。
春哥说最近确实有更新导出工具,但只是fbs里新增加了一个类型,老工程不该受到影响才对。

直球分析

既然没有头绪就大力出奇迹,直接分析下数据内容;可以看到区别其实是childrencustomClassName对应数据位置颠倒了下。

table NodeTree
{
classname:string; //红框
children:[NodeTree]; //黄框
options:Options; //绿框
customClassName:string; //蓝框
}

翻下对应代码,看起来很像mBuilder.CreateVector(children)mBuilder.CreateString(customClassName)执行顺序颠倒了。

return CreateNodeTree(mBuilder,
mBuilder.CreateString(classname),
mBuilder.CreateVector(children),
options,
mBuilder.CreateString(customClassName));

这时候突然想到一个问题——好像编译器本来就不保证这种情况的执行顺序来着… 翻了下order of evaluation,确实是这样子来着。

验证猜想

接下来要想办法验证一下,所以我本地也编译了一个exe跑了下对比…结果发现不同的地方更多了orz

不过一样的思路,看了下其实就是另一个table中元素顺序颠倒, 对应的代码为

auto csparsebinary = CreateCSParseBinary(mBuilder,
mBuilder.CreateString(_csdVersion),
mBuilder.CreateVector(_textures),
mBuilder.CreateVector(_texturePngs),
nodeTree,
action,
mBuilder.CreateVector(animationInfos),
animationFrame,
mBuilder.CreateString(animationName)
);

改成明确调用顺序的版本后可以验证: 上面这个版本生成的csb和我刚才生成的一样,下面这个版本生成的csb和春哥生成的csb一样;。
区别其实就是test4test5两行倒一下。

auto test5 = mBuilder.CreateString(animationName);
auto test4 = mBuilder.CreateVector(animationInfos);
auto test3 = mBuilder.CreateVector(_texturePngs);
auto test2 = mBuilder.CreateVector(_textures);
auto test1 = mBuilder.CreateString(_csdVersion);
auto csparsebinary = CreateCSParseBinary(mBuilder,
test1,
test2,
test3,
nodeTree,
action,
test4,
animationFrame,
test5
);
auto test4 = mBuilder.CreateVector(animationInfos);
auto test5 = mBuilder.CreateString(animationName);
auto test3 = mBuilder.CreateVector(_texturePngs);
auto test2 = mBuilder.CreateVector(_textures);
auto test1 = mBuilder.CreateString(_csdVersion);
auto csparsebinary = CreateCSParseBinary(mBuilder,
test1,
test2,
test3,
nodeTree,
action,
test4,
animationFrame,
test5
);

索性找春哥要了下他生成的exe比了下,确实在没有手动声明的情况下、函数调用顺序是不一样的:

修复办法

确定原因之后,修复的思路就非常清晰了: 全部改成手动调用从而明确mBuilder.Create***的调用顺序。

我后来翻了下官方示例写法,确实是一路手动声明下来; 换言之来自cocos2d-x的祖传实现用到现在其实是一直有风险的emmm

BTW: 我一开始怀疑是不同msvc版本下有区别,后来发现本地在debug和release下都有不少区别… 不过既然是合法行为、也懒得纠结了