Project1

标题: 【元编程的小小应用】追踪未释放的对象 [打印本页]

作者: taroxd    时间: 2014-8-2 19:46
标题: 【元编程的小小应用】追踪未释放的对象
本帖最后由 taroxd 于 2014-8-2 20:03 编辑

写这个东西的原因,是某游戏制作组表示内存溢出,然后我就帮他们检查了一下。
一检查吓一跳,原来著名的 Yanfly Battle Engine 还有 Fuki 对话框都有未释放 Sprite 或者 Bitmap 的问题!

我想说的,是这些 Ruby 的技巧展现出的威力。
@喵呜喵5 和我说,制作一个游戏不需要超过 F1 手册的 Ruby 知识。
但我觉得,对 Ruby 理解的更深入一点总是好的。
至少,不用这一种【内省】的方式,是很难找出别人写的代码中的 bug 的。

代码中的 def_xxx 可以理解为我的免 alias 补丁(也是元编程!),也可以简单地理解为伪代码,表示在原方法前后补充执行一段代码。
请把下面的脚本当作一个学习脚本的示例,而不是把这个脚本直接拿去用。因为不会脚本的人,你用了也看不懂输出结果。

RUBY 代码复制
  1. class Viewport
  2.   def_before(:dispose) { @__dispose__ = true }
  3.   def disposed?; @__dispose__; end
  4. end
  5.  
  6. need_dispose = [Bitmap, Sprite, Window, Plane, Tilemap, Viewport]
  7. callers = {}
  8. callers.compare_by_identity
  9. not_disposed = []
  10.  
  11. need_dispose.each do |klass|
  12.   klass.class_eval do
  13.     def_after(:initialize) {|*| callers[self] = caller }
  14.   end
  15. end
  16.  
  17. Scene_Base.class_eval do
  18.   def_after :terminate do
  19.     need_dispose.each do |klass|
  20.       ObjectSpace.each_object(klass) do |obj|
  21.         not_disposed.push(obj) unless obj.disposed?
  22.       end
  23.     end
  24.   end
  25.  
  26.   def_after :update do
  27.     return unless Input.trigger?(:ALT) # 按下ALT键时显示检测结果
  28.     puts not_disposed.delete_if(&:disposed?) # 输出所有未释放的对象
  29.     puts callers[
  30.       not_disposed.shuffle.find do |obj|
  31.         # 随机找出一个不是 Cache 中生成的,未释放的对象,输出其 caller
  32.         # 如果不存在,则输出一个空行
  33.         callers[obj] && callers[obj].none? {|str| str.start_with?('{0004}') }
  34.       end
  35.     ]
  36.   end
  37. end


Q: 找到未释放的对象之后,为什么不直接释放该对象,而是追踪该对象的生成呢?
A: 我希望从脚本的根源解决问题,而不是掩盖一个脚本设计者的疏忽。
作者: taroxd    时间: 2014-8-2 20:51
本帖最后由 taroxd 于 2014-8-2 20:57 编辑

@3106345123  真的,只要求释放的话毫无难度,但是我是真心希望从根源上解决问题,而不是依靠这一种手段。

RUBY 代码复制
  1. class Scene_Base
  2.   alias auto_dispose_terminate terminate
  3.   def terminate
  4.     auto_dispose_terminate
  5.     [Sprite, Window, Plane, Tilemap, Viewport].each do |klass|
  6.       ObjectSpace.each_object(klass, &:dispose)
  7.     end
  8.   end
  9. end

作者: 喵呜喵5    时间: 2014-8-2 21:23
实际上我还是觉得制作游戏,至少用RM制作游戏是不需要掌握到这种程度的。在写代码时如果能养成良好的代码习惯即使不使用元编程来检查错误也可以啊。
当然我也不是说不应该掌握那么多,如果只是单纯作为一个程序员或者写脚本的,多掌握总比不会要好。

就我自身的情况来说的话,我是只掌握稍微高于自己必要之上一点点的知识,因为我一直都是单干的,美工音乐剧本等等各个方面都必须分出精力来学习,只是专精脚本的话估计做不出游戏吧【躺
作者: 无脑之人    时间: 2014-8-5 17:48
我还以为是用元编程帮GC擦那啥呢……结果是把所有没释放的全部释放掉了吗……
你让Cache储存Bitmap的机制怎么办,如果有一直显示的东西【比如鼠标】怎么办……
帮忙找出来的想法挺好,不过擦的不太完美的样子/w\
作者: taroxd    时间: 2014-8-5 17:52
本帖最后由 taroxd 于 2014-8-5 18:01 编辑
无脑之人 发表于 2014-8-5 17:48
我还以为是用元编程帮GC擦那啥呢……结果是把所有没释放的全部释放掉了吗……
你让Cache储存Bitmap的机制怎 ...


1. 顶楼的脚本只会追踪未释放的对象而不会去释放。
  这是我推荐的方式,也是我自己的使用方式。在实际应用中,擦屁股擦得非常完美。储存在常量中的 bitmap,以及 Cache 中的 bitmap 都不会被追踪到。
2. 沙发的脚本并没有释放 Bitmap,完全不存在这个问题(我也很不喜欢这个脚本,但是有人要)

我不知道你有没有好好看过上面的脚本
作者: 喵呜喵5    时间: 2014-9-21 22:22
本帖最后由 喵呜喵5 于 2014-9-21 22:52 编辑

为什么 p @viewport.class.instance_methods 的结果里找不到 disposed? 方法……



P.S

怀着喜悦的心情把Sprite的问题解决了,原来是测试用代码里手滑多写了一个super……
作者: 喵呜喵5    时间: 2014-9-22 12:40
仔细一想viewport和spirit出现原因一样,都是测试时用的代码不遵守自己的规范造成的,仍然和我自己正式的代码毫无关系...




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1