遇到了写程序到现在最神秘的一个问题。是跑在wasm里的一个c sharp 脚本项目。
大致上是捕捉指针的按住拖拽,记录下轨迹,然后在轮到canvas绘制画面的时候在轨迹上增加形变波动,模拟手指的滑动导致的画面变形。
编写者的代码或许是ai写的,自己也不是很清楚。但总而言之,就是形变只有首次点击的位置,后续的拖拽一直不生效。于是此人便加了日志,在手指滑动时输出记录下的形变数据。
然后……这项目就……好了。滑动指针时形变就正常了。
去掉日志,就又坏了。
于是此人就把代码拿给我看。我试了下也能复现,就很神奇。
考虑方向1: 滑动时的形变计算逻辑在编译时被优化没了?而日志的存在使得结果必须强制被输出,所以加了日志导致这些逻辑回来了?
而倾向于否定这一结论的论据:更新轨迹和绘制path的时间点是分开的,轨迹形变是保存在静态数组中的,没有道理被优化没掉。除非wasm中的c sharp 存在什么此前未知的问题。
考虑方向2:日志api是wasm环境宿主提供的api,strokepath也是wasm环境宿主提供的api。而在render canvas的时候由脚本环境反向发送数据给宿主可能会引起交互区域数据堆栈被破坏,导致奇怪的问题。(然而我不持有交互逻辑的代码,所以这只是一个推测,但可以确定的是,交互逻辑是在wasm环境中开辟了一段共享内存而实现的)
而倾向于否定这一结论的论据:一般来说这种情况即使发生也应该是我调了外部提供的日志api,而导致堆栈坏了。但现实情况是反过来,我不调日志api才会坏。
测试1: 将日志改为输出一段普通字符串,不再输出形变数据。
结果:项目又坏了,拖拽形变再次失效。
这似乎能佐证考虑方向1,然而——
测试2:将日志改为输出项目中的其它与形变逻辑无关的变量值。
结果:拖拽形变又正常了。
!好神秘。和计算结果其实完全没关系啊。
测试3:测试输出(“1”+"2");
结果:拖拽形变失效。
考虑:难道必须是输出变量值么?然而——
测试4:测试输出( $ "1+2 = {1+2}")
结果:形变正常
嗯?难道只是因为字符串内插?(注:原本一开始令项目运作正常的形变输出就用了字符串内插)
辅助测试5:测试输出($ "{1+2}")
结果:失效。
多次测试结论:必须至少有有一个literal成员,比如 $ "a{1}"就可以,只有 $ "{1}"就不行。string加法则不行。
emmm... 到了这个点上,只怕和日志本身都无关了。
干脆不使用日志,写了一句明显会被优化掉的句子。
测试6: var a = $ "a{1}";
结果:形变有效
果然和日志都没关系,只是因为用了字符串内插……(注: a并没有被使用)
测试7: 将字符串内插语句放进不会被执行的分支中。
if (test) // test永远为false
{
$ "a{1}".ToString();
}
结果:有效。即使大括号中的语句永远不会被执行。
emmmmm...
顺便,理所当然的, if (false) 果然是无效的,因为整段会被优化没掉。
考虑:+没有用,字符串内插却有用,.net 版本较高。考虑是InterpolatedStringHandler的原因,与stringbuilder无关。于是
测试8:
if (test)
{
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new ();
defaultInterpolatedStringHandler.AppendLiteral(null!);
// defaultInterpolatedStringHandler.AppendFormatted(2);
}
结果:有效
测试结果:AppendFormatted语句根本不需要存在。只需要AppendLiteral就可以。只不过普通情况下$ "a"会被直接优化为"a"而已。
emmm...也就是说AppendLiteral这个函数的存在改变了什么东西。即使它不实际被执行。
由于手头有许多其它事情,项目交还给原主继续检验。目前阶段由于缺少宿主的源码,导致不是很容易直接在宿主端直接检验实际接收到的参数。但目前由于已经找到了无副作用地输出日志的方法(即使用+来进行stringbuild而非使用字符串内插),应该能指望之后有进一步的进展。于是之后就等原主自己试了。
发布于 上海
