赞 | 6 |
VIP | 0 |
好人卡 | 0 |
积分 | 8 |
经验 | 0 |
最后登录 | 2024-4-2 |
在线时间 | 70 小时 |
Lv2.观梦者
- 梦石
- 0
- 星屑
- 777
- 在线时间
- 70 小时
- 注册时间
- 2017-12-2
- 帖子
- 14
|
加入我们,或者,欢迎回来。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
之前修改脚本时,有些情况下是在自带脚本的某些方法上做修改,还需要调用原方法的,为了和其它脚本兼容,一般用alias+重定义,有时会嫌alias写得麻烦,想写一个自动alias一个时间戳别名,于是学了一下法术(就是那本《Ruby元编程》),最早想通过写模板代码走eval,后来随着工作忙碌,写一半忘了。
这几天春节在家有空了,整理了下之前写的东西,重新看了眼书,重写了下代码,就不用eval了。
- #encoding: utf-8
- # -------------------------------------------------------------------
- # - KRuby module by KurozawaRuby
- # -------------------------------------------------------------------
- module KRuby
- VERSION = '1.0.0215'
- end
- # -------------------------------------------------------------------
- # - KRuby::ClassExt by KurozawaRuby
- # -------------------------------------------------------------------
- module KRuby::ClassExt
- private
- # -----------------------------------------------------------------
- # - 在某方法调用后执行 block。
- # - block 参数即原方法参数
- # - 不会修改方法的返回值
- # - 使用: after:method_name { |*args| do_sth }
- # -----------------------------------------------------------------
- def after(method_name, &block)
- return unless block_given?
- old = instance_method(method_name)
- define_method(method_name) do |*args, &b|
- result = old.bind(self).call(*args, &b)
- instance_exec(*args, &block)
- result
- end
- end
- # -----------------------------------------------------------------
- # - 在某方法调用后执行 block。
- # - block 第一个参数为原方法返回值,后续参数为原方法参数
- # - 该方法的返回值会被修改为block的返回值
- # - 使用: after!:method_name { |result, *args| do_sth }
- # -----------------------------------------------------------------
- def after!(method_name, &block)
- return unless block_given?
- old = instance_method(method_name)
- define_method(method_name) do |*args, &b|
- result = old.bind(self).call(*args, &b)
- instance_exec(result, *args, &block)
- end
- end
- # -----------------------------------------------------------------
- # - KRuby::ClassExt::WrapMethod
- # - 作为 around! 块的参数使用,封装原始方法及调用参数
- # -----------------------------------------------------------------
- class WrapMethod
- attr_reader :org_method, :args, :block
- def initialize(old, args, block, obj)
- @org_method = old
- @args = args
- @block = block
- @obj = obj
- end
- # ---------------------------------------------------------------
- # - 调用原方法
- # ---------------------------------------------------------------
- def call
- @org_method.bind(@obj).call(*@args, &@block)
- end
- end
- # -----------------------------------------------------------------
- # - 使用 block 替换某方法,但可以在 block 内调用原方法。
- # - block 的参数为 WrapMethod 对象,由原方法、参数等构成
- # - 在 block 内可通过 WrapMethod#call 调用原方法
- # - 使用: around!:method_name { |org| org.call && do_sth }
- # -----------------------------------------------------------------
- def around!(method_name, &block)
- return unless block_given?
- old = instance_method(method_name)
- define_method(method_name) do |*args, &b|
- instance_exec(
- WrapMethod.new(old, args, b, self),
- &block
- )
- end
- end
- end
- Object.send(:extend, KRuby::ClassExt)
复制代码
这堆代码提供几个类宏,分别是 after, after!, around! 代码不长,
首先是 after 和 after! 这两个,是表示在某个方法调用后执行,用法很简单
- class << DataManager
- after:load_database do
- puts "load complete"
- end
- end
- class Game_Battler
- after:refresh do
- puts "actor #{name} with hp_rate = #{hp_rate}"
- end
- end
复制代码
这两段分别是在 DataManager.load_database 这个静态方法调用后和 Game_Battler#refresh 这个实例方法调用后在控制台输出一些东西,可以看到这里的 self 也是处理好了的。
两个after的区别是不带感叹号的不会改变方法返回值,然后块参数也有一点区别,带感叹号的第一个参数是原方法返回值。
如果要在方法调用前执行某段代码,照着两个 after 改一下也可以整出类似 before 和 before! 这样的东西。也可以使用下面说的 around! 实现。
然后是 around! 这个有点向 java aop 里面的 @around 切面,你需要手动执行原方法,比起 after 之流灵活很多,其实之前的 after 也可以使用 around! 实现(下面一段代码可替换上面代码里 after 的实现)
- def after(n, &b)
- return unless b
- around!(n) do |org|
- res = org.call
- instance_exec(*org.args, &b)
- res
- end
- end
复制代码
类宏 around 的块参数只有一个,是 KRuby::ClassExt::WrapMethod 类的实例,可以使用 WrapMethod#call 调用原方法并获取返回值,也可以获取原方法的方法对象(org_method)和入参(args, block)
性能损失:
测下性能,使用以下跑分代码(after2即为之前的 around! 实现的 after )
- module Test end
- class << Test
- def a
- test
- end
-
- def b
- test
- end
-
- def c
- test
- end
-
- def d
- test
- end
-
- def test
- "a" * 100
- end
- after:a do 1 end
- after2:b do 1 end
- alias c_org c
- def c
- c_org
- 1
- end
- end
- Benchmark.bm do |x|
- x.report('after') { 1000000.times { Test.a } }
- x.report('after2') { 1000000.times { Test.b } }
- x.report('alias') { 1000000.times { Test.c } }
- x.report('org') { 1000000.times { Test.d } }
- end
复制代码
我机器的结果 # i7-9750H ruby 1.9.2p290 (2011-07-09) [i386-mingw32]
- user system total real
- after 0.906000 0.031000 0.937000 ( 0.942526)
- after2 1.438000 0.000000 1.438000 ( 1.437284)
- alias 0.469000 0.000000 0.469000 ( 0.458915)
- org 0.437000 0.016000 0.453000 ( 0.463683)
复制代码
可以看到百万次调用,使用 alias 速度和原始的差不多,而使用 after 要慢很多(0.5秒)而使用 around! 实现的 after2 又比 after 还慢0.5秒
不过一百万次调用0.5秒-1秒的性能损失我觉得也可以接受吧,rm 里就算是每帧都调10次也不到1毫秒的影响,更何况我们实际使用基本不可能到那种程度。
上述代码(除了跑分)在我的VA是能跑的,XP和VX没试过。
|
|