Project1
标题:
最近重学了下ruby,写了个替代 alias 的东西自娱自乐
[打印本页]
作者:
KurozawaRuby
时间:
2021-2-17 11:22
标题:
最近重学了下ruby,写了个替代 alias 的东西自娱自乐
之前修改脚本时,有些情况下是在自带脚本的某些方法上做修改,还需要调用原方法的,为了和其它脚本兼容,一般用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没试过。
作者:
MCCF
时间:
2021-2-17 13:02
我记得taroxd的核心引擎有这些东西(
作者:
PLeaseS
时间:
2021-2-17 13:57
alias是什么东西啊???????
欢迎光临 Project1 (https://rpg.blue/)
Powered by Discuz! X3.1