2018-09-25
BUG
loading时偶尔卡住bug
表现:无法继续,无报错,出现概率:2/10,所有平台都会重现,引入声音加载后更容易重现。
-
调试目标:由于声音容易重现,所以从声音模块入手调试。
-
重现步骤:发现将cpu速度下降,和加载大量声音,和开启大量加载线程,每次进游戏后几乎必现。必现很重要,不然无法确定修改后的bug是否正确。碰到偶然bug,第一时间是看代码逻辑,没法从逻辑中找到问题,只能创造必现的条件。
-
步骤:
-
如何获取卡住的目标模块堆栈信息?卡住后程序实际还在运行,这时候打算加入断点,但目标模块已经不在运行,加入断点也无效,这时候加入的地方是一个系统Timer,当卡住后,在Timer的callback中打断点,游戏主程序会被断下来。
-
在Timer打断点,如何才能获取目标模块的内存信息?这时候可以预先在目标模块加入调试代码,例如window.__xxxModule = RES.instance; 将目标的的局部变量引用存到全局变量里面,这样断点的时候可以在watch窗口输入window.__xxxModule查看目标的内存信息。
-
查看window.__xxxModule模块后,发现没有声音模块的引用,无法获得声音模块的对象信息,重新修改引擎代码,将声音模块的引用加入到其父模块中,例如RES.instance.sound = sound; 注意新加的引用最好加入到其父模块,阅读大概的加载流程和父子模块的生命周期是否一致,子模块可以由父模块哪些变量管理。加完子引用后重新执行步骤1。
结论:发现加入引用后bug无法重现啊。经分析是白鹭的资源加载模块,没管理子加载模块的引用,导致浏览器在gc的时候被回收,callback无法回调结果卡住。
修复:声音模块引用有问题,需要确定其他加载模块是否也存在同样问题。经发现,图片加载模块也是有bug的,图片加载模块使用了缓存池,前4组线程一旦成功就会被缓存池引用,但万一刚加载就被gc掉了,也会导致卡住的问题。所以都要给父模块加入子模块的引用才行。
关闭某UI时,偶尔报错
表现:报错堆栈被中断,无法获取有效信息。在某些情况下,Timer或者Promise回调回来的堆栈不完整,无法获取所有堆栈内容。出现概率 2/50。
-
调试目标: 该UI模块
-
重现步骤:由于出现概率极低,首要任务是将重现概率提高。
-
步骤:
- 由于问题出现在关闭UI的瞬间,只要提升关闭次数,就能提高重现概率,编写测试代码,
while(1000) {
UIMgr.open(xxx);
UIMgr.close(xxx);
}
若以上代码不出错,考虑到偶现的都是定时器或者动画引起的,需加入延迟关闭的测试代码,
while(1000) {
UIMgr.open(xxx);
Timer.After(1000, UIMgr.close(xxx));
}
结论:经发现,是有子模块的对象没被释放,对象内的定时器还在运行,回调的对象已经无效。
修复:在onExit中加入对子模块的释放。
某动画移动的位置总是不对
表现:某一时刻出现很多飞金币飞资源动画,但只有飞金币动画位置不对,查看代码逻辑没发现问题。
-
调试目标:仅仅针对飞金币动画。
-
重现步骤:将飞金币动画和部分模块,迁到登录界面,运行时看是否重现。这里就突显函数封装的重要性,好的程序结构,调试bug也更方便。函数必须是要功能单一和完整的,要做到这样,就要求函数尽量只依赖参数,然后返回结果。少用this对象。尤其是UI动画,按道理来说应该比较独立,对其他UI和业务逻辑依赖比较少才对。
游戏越来越卡
表现:刚进游戏流程,游戏后期很变卡和发热严重。
-
调试目标:猜测内存泄漏问题
-
重现步骤:有可能是常用的UI模块或者某重复创建销毁的大对象释放有误导致的。
-
步骤:
- 假设是最消耗内存的骨骼动画释放问题:在游戏登录界面,编写测试代码,增加两个button: add/sub, addBtn创建1000个骨骼动画, subBtn删除1000个骨骼动画,重复操作几次,若是界面骨骼动画数量为0时,内存和cpu都没法下降,必然是骨骼动画释放机制有错,导致内存泄漏。若是UI问题,操作方式也差不多。
UI关闭时骨骼动画释放偶尔报错
表现:偶尔会出现调用到无效骨骼对象的报错堆栈信息
-
调试目标:白鹭的骨骼动画,创建是需要加入全局时钟 clock.add(arm); 移除时需要从全局时钟中移除 clock.remove(arm);堆栈信息是从remove中的报错,初步怀疑是没有remove已经释放的arm导致的。但看代码没看出问题所在(其实是有问题的)
-
重现步骤:出现概率比较低,无法必现。但通过重复开关UI的方式统计错误信息。
-
步骤:
-
给每一个arm对象增加唯一标志:
this.name = Math.random();
在clock.add(arm)
的前一句代码中加入console.log("add:" + this.name); close.remove(arm)
前一句代码中加入console.log("remove:" + this.name);
通过输出log,观察每个对象创建是否都已经被释放。用眼睛看log比较累,其实可以通过写测试模块,封装好关键信息,定时收集,不匹配就输出断言,这样就不需要用眼睛对比log信息。 -
经发现,前两次的UI创建和释放log交错输出,统计发现总是差一次释放。
结论:是由于关键的add,remove代码不同步导致的,主要原因是创建骨骼动画是异步回调的。例如:
UIxxx {
onEnter() {
let cb = (arm)=> {
clock.add(arm)
this.arm = arm;
}
createArm(cb)
}
onExit() {
if (this.arm) {
clock.remove(this.arm);
this.arm = null
}
}
}
修复:关键的add,remove尽量不要引入其他条件判断(谨慎)。进出的逻辑操作一定要尽量贴近对象的创建和释放代码。
某IOS版本上,拖放对象时出现黑块
表现:只在特定IOS版本上才能重现
-
调试目标:采用debug模块,手机直接输入debug地址
-
重现步骤:只要拖动就会显示黑块,必现。
-
步骤:
-
初步怀疑是UI中的某些节点干扰,采用排除法,先将无关的节点隐藏。发现还是能重现。
-
UI没找到问题,接着采用代码屏蔽法,先将拖动的代码注释掉,发现表现正确了。初步确定问题所在。
-
逐步注释代码,最后将hitTest(true);修改为hitTest();发现表现正确,而且操作逻辑无误。
结论:使用了特定ios版本不支持的像素检查属性,预估是白鹭的bug.
修复:修改为hitTest()就行了,正常逻辑本来就不需要采用像素检查。
IPHONE SE 二维码无法生成
表现:只在特定iphone se上才能重现
-
调试目标:采用debug模块,手机直接输入debug地址
-
重现步骤:必现。要生成二维码的游戏步骤过多,直接将生成代码贴到登录界面,省去loading时间。
-
步骤:
-
查看系统Image加载dataURL的代码,经发现是加载dataURL时WebImage发生错误,但没有输出具体错误信息。
-
WebImage代码不多,只有十几行,采用代码注释排除法。最后将Image.crossOrigin注释掉。发现二维码能生成了。
结论:iphone_se不支持匿名跨域创建Image DataURL。
修复:在Image loader里面,判断当前src是否dataURL,若是临时关闭跨域属性。
IPHONE X 游戏模糊
表现:经发现,不仅仅是IPHONE X,只要在特定的宽高比就会变得模糊
-
调试目标:采用浏览器
-
重现步骤:webgl模式下拖放浏览器,整个游戏会模糊,必现。但canvas没问题。
-
步骤:
-
怀疑白鹭改出bug了
-
下载白鹭github上面的工程。
-
采用git log -S 命令刷选 模糊 关键字。查看修改的代码,尝试还原测试。
结论:发现有一次代码提交,是修正字体模糊,针对canvas所加的。
if (egret.Capabilities.renderMode == "canvas") {
canvasScaleX = Math.ceil(canvasScaleX);
canvasScaleY = Math.ceil(canvasScaleY);
}
修复:发现将针对canvas的条件后注释,模糊问题基本解决;
总结
- 创建测试环境
- 创建必现条件
- 编写测试代码
- 注释代码(参考二分查找算法)
- 查找历史记录log