查看: 2097|回复: 6

[RMVA发布] 【轮子】Blockade,Fiber 的无脑抽象

[复制链接]

唯一的信徒

梦石
0
星屑
1697
在线时间
1359 小时
注册时间
2013-1-29
回帖
1537
发表于 2017-3-10 08:20:34 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 LBQ 于 2017-3-10 12:11 编辑

Note:这个主要是 Love2d 的 Hump.timer 库的 Ruby 版本,目标是用给自己写脚本无脑直接用渐变移动之类的,主要是“工具库”而不是“轮子库”,但是 Fiber 干这种事情很多人都知道所以,这个很大程度上只是方便用的工具,所以也会带补间功能之类的……
问题:
从事件转移过来的写法
1. 做 A 这件事
2. 等 30 帧
3. 做 B 这件事

写成了
[pre lang="ruby"]do_a
30.times {Graphics.update}
do_b[/pre]

从这里引申出来的问题
  1. 25.times do |x|
  2.   xxx.opacity -= 10
  3.   Graphics.update
  4. end
复制代码


这些都是从长远来看不合适的,而最自然的解决方式就是用 Fiber 了,这也是事件解析器中能够制造出“等待”效果的原理。

那么 Blockade 就是以 Fiber 为原材料制造的一个小的库,顺便借用了别人写的 tween 让 RM 也容易写出一些非线性的补间帧。

使用说明:
生成 Blockade 实例之后,对实例需要在每帧使用 update。
例子:

[pre lang="ruby"]class Scene_Demo < Scene_Base
  def start
    super
    @blockade = Blockade.new
    @blockade.schedule do |f|
      msgbox "player enters"
      wait 60 # 等待 60 帧
      msgbox "player dies"
      wait 80 # 等待 80 帧
      SceneManager.return
    end
  end
  
  def update
    super
    @blockade.update
  end
end[/pre]

好处就是可以同时运行多个,随意嵌套。
[pre lang="ruby"]    @blockade.schedule do |f|
      msgbox "player enters"
      wait 60 # 等待 60 帧
      @blockade.schedule do |f|
        msgbox "haha"
        wait 40
        msgbox "bye"
      end
      wait 80 # 等待 80 帧
      SceneManager.return
    end[/pre]

还有其他一些方法:
[pre lang="ruby"]@blockade.every(60) {|f| puts "hi"} # 每 60 帧执行
@blockade.after(120) {|f| puts "hi"} # 在 120 帧之后执行
@blockade.during(20..30) {|f| puts "hi"} #只在 20 到 30 帧时执行[/pre]

补间:
直接盗用了别人写的 Easing 库,在这里可以这么用:
[pre lang="ruby"]
# 在 Scene 初始化时候
@sprite = Sprite.new
@sprite.bitmap = Cache.battler("Slime",0 )
@sprite.opacity = 0
@blockade.tween(:ease_in_quad, @sprite, [:x, :y, :opacity], [200, 300, 255], 60) # 用 ease_in_quad 补间函数,将 @sprite 的 x, y, opacity 属性分别在 60 帧内补间到 200, 300, 255 的值
[/pre]

所有补间函数为了方便放在下面:

  1.   def linear_tween(t, b, c, d)
  2.   def ease_in_quad(t, b, c, d)
  3.   def ease_out_quad(t, b, c, d)
  4.   def ease_in_out_quad(t, b, c, d)
  5.   def ease_in_cubic(t, b, c, d)
  6.   def ease_out_cubic(t, b, c, d)
  7.   def ease_in_out_cubic(t, b, c, d)
  8.   def ease_in_quart(t, b, c, d)
  9.   def ease_out_quart(t, b, c, d)
  10.   def ease_in_out_quart(t, b, c, d)
  11.   def ease_in_quint(t, b, c, d)
  12.   def ease_out_quint(t, b, c, d)
  13.   def ease_in_out_quint(t, b, c, d)
  14.   def ease_in_sine(t, b, c, d)
  15.   def ease_out_sine(t, b, c, d)
  16.   def ease_in_out_sine(t, b, c, d)
  17.   def ease_in_expo(t, b, c, d)
  18.   def ease_out_expo(t, b, c, d)
  19.   def ease_in_out_expo(t, b, c, d)
  20.   def ease_in_circ(t, b, c, d)
  21.   def ease_out_circ(t, b, c, d)
  22.   def ease_in_out_circ(t, b, c, d)
复制代码


Block 参数

  1. schedule {|f|
复制代码

所给出的 f 参数是用于进行多线程的 fiber 实例,有 timer 属性可以用来计时。

[pre lang="ruby"]@blockade.schedule do |f|
  p f.timer #0
  wait(60)
  p f.timer #60
end[/pre]

脚本如下:
[pre lang="ruby"]
=begin
Blockade.rb | Port of HUMP.timer to Ruby, aimed at RGSS
===
Early Beta v0.1.0
============================================================
It is essentially my attempt to abstract away fiber for async code.

This script is licensed under the APACHE-2.0 license, see
http://www.apache.org/licenses/LICENSE-2.0.html for more details.

Heavily inspired by HUMP.timer for Love2d:
http://hump.readthedocs.io/en/latest/timer.html
=end

=begin
This script uses the easying methods ported to Ruby by Damián Silvani,
see https://github.com/munshkr/easing-ruby for more details
=end

=begin
Copyright (c) 2013 Damián Silvani

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=end

module Easing
  extend self

  def linear_tween(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    c * t / d + b
  end

  def ease_in_quad(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c*(t/=d)*t + b;
  end

  def ease_out_quad(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return -c *(t/=d)*(t-2) + b;
  end

  def ease_in_out_quad(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    t /= d / 2
    return c / 2*t*t + b if (t < 1)
    t -= 1
    return -c/2 * (t*(t-2) - 1) + b
  end

  def ease_in_cubic(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c*(t/=d)*t*t + b
  end

  def ease_out_cubic(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c*((t=t/d-1)*t*t + 1) + b
  end

  def ease_in_out_cubic(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c/2*t*t*t + b if ((t/=d/2) < 1)
    return c/2*((t-=2)*t*t + 2) + b
  end

  def ease_in_quart(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c*(t/=d)*t*t*t + b
  end

  def ease_out_quart(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return -c * ((t=t/d-1)*t*t*t - 1) + b
  end

  def ease_in_out_quart(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c/2*t*t*t*t + b if ((t/=d/2) < 1)
    return -c/2 * ((t-=2)*t*t*t - 2) + b
  end

  def ease_in_quint(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c*(t/=d)*t*t*t*t + b
  end

  def ease_out_quint(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c*((t=t/d-1)*t*t*t*t + 1) + b
  end

  def ease_in_out_quint(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c/2*t*t*t*t*t + b if ((t/=d/2) < 1)
    return c/2*((t-=2)*t*t*t*t + 2) + b
  end

  def ease_in_sine(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return -c * Math.cos(t/d * (Math::PI/2)) + c + b
  end

  def ease_out_sine(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c * Math.sin(t/d * (Math::PI/2)) + b
  end

  def ease_in_out_sine(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return -c/2 * (Math.cos(Math::PI*t/d) - 1) + b
  end

  def ease_in_expo(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return (t==0) ? b : c * (2 ** (10 * (t/d - 1))) + b
  end

  def ease_out_expo(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return (t==d) ? b+c : c * (-2**(-10 * t/d) + 1) + b
  end

  def ease_in_out_expo(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return b if t == 0
    return b + c if t == d
    return (c/2) * 2**(10 * (t-1)) + b if ((t /= d/2) < 1)
    return (c/2) * (-2**(-10 * t-=1) + 2) + b
  end

  def ease_in_circ(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b
  end

  def ease_out_circ(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return c * Math.sqrt(1 - (t=t/d-1)*t) + b
  end

  def ease_in_out_circ(t, b, c, d)
    t = t.to_f; b = b.to_f; c = c.to_f; d = d.to_f

    return -c/2 * (Math.sqrt(1 - t*t) - 1) + b if ((t/=d/2) < 1)
    return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b
  end
end

class Blockade
  class BFiber < Fiber
    attr_reader :alive
    attr_accessor :timer
    def initialize *args
      super *args
      @alive = true
      @timer = 0
    end
   
    def resume *args
      begin
        super *args
        @timer += 1
      rescue FiberError => e
        @alive = false
      end
    end

    def alive?
      @alive
    end
  end
  
  def initialize
    @pool = []
    @timer = 0
  end

  def fmap_tween meth, start, final, t, d
    delta = start.zip(final).map{|e| e[1] - e[0]}
    start.zip(delta).map do |tup|
      Easing.send(meth, t, *tup, d)
    end
  end
  
  def tween meth, obj, attrs, final, d
    initial = attrs.map{|sym| obj.send(sym)}
    schedule do |f|
      loop do
        timer = f.timer
        ans = fmap_tween meth, initial, final, timer, d
        ans.each_with_index do |rhs, i|
          obj.send("#{attrs}=", rhs)
        end
        yield f, obj if block_given?
        if timer < d
          wait
        else
          break
        end
      end
    end
  end
  
  def schedule &b
    b.binding.eval <<-HERE
    def wait t=1
      t.times {Fiber.yield}
    end
    HERE
    ele = BFiber.new(&b)
    @pool << ele
    return ele.hash
  end

  def after time, &b  
    schedule do |tick|
      wait(time)
      tick.timer -= time
      b[tick]
    end
  end

  def every time, &b
    schedule do |tick|
      loop do
        if tick.timer % time == 0
          instance_exec tick, &b
        end
        Fiber.yield
      end
    end
  end

  def during range, &b
    schedule do |tick|
      loop do
        if range.include?(tick.timer)
          instance_exec tick, &b
        end
        Fiber.yield
      end
    end
  end

  def cancel k
    @pool.delete_if{|it| it.hash == k}
  end

  def clear
    @pool = []
  end

  def update
    @pool.each do |f|
      f.resume(f)
    end
    @pool.keep_if{|v| v.alive?}
    @timer += 1
  end
end
[/pre]

觉得也许每个 Fiber 都应该是一个类 Blockade 的实例…… 但是现在大脑不清醒于是先这么写着,出了 bug 有问题希望告诉我一下这样我能修改……

评分

参与人数 2星屑 +132 收起 理由
zaiy2863 + 66 轮子
fux2 + 66 不懂反正醋虾

查看全部评分

『我只是一个正在潜心修炼的渣乐师罢了』
Dear Time\(^o^)/~


假如上面的图片挂了的话麻烦各位去发个帖 @ 一下 orzFly 让他修复 deartime
梦石
0
星屑
4623
在线时间
1207 小时
注册时间
2016-4-7
回帖
944

开拓者

发表于 2017-3-10 11:05:57 | 显示全部楼层
噗 和我的 svent 类似嘛
不过我那个是事件处理方向上的
还有 可以和大佬交流交流frp么

点评

是的,ES2015有和fiber类似的generator  发表于 2017-3-10 12:39
LBQ
那么我也不是特别清楚,我记得 ES2015 是有 Generator 的所以也许可以去用 Generator 写…… 但是我真的不清楚  发表于 2017-3-10 12:13
最近想做新的事件处理(主要是js没fiber囧)打算换到frp之类的。  发表于 2017-3-10 12:07
LBQ
唉果然写的人多(,frp 啥的找专门的 FP 群吧我也只是知道很少的东西……  发表于 2017-3-10 12:04
附庸的附庸不是我的附庸,女儿的女儿还是我的女儿。CK2沉迷ing
回复

使用道具 举报

梦石
0
星屑
4623
在线时间
1207 小时
注册时间
2016-4-7
回帖
944

开拓者

发表于 2017-3-19 10:25:16 | 显示全部楼层
本帖最后由 shitake 于 2017-3-19 10:44 编辑

@喵呜喵5  @LBQ
然而 ES6 的 generator  设计我觉得是有很大问题。他不像 ruby 的 fiber 那么灵活和自由。我不知道是否有什么办法可以越过这个限制。
关于这点的细节我记得好像在这里有提到过: https://github.com/molingyu/blog/issues/1

不过用 FRP 的话可以直接写原生的 ES5,这可以和 RMMV 保持一致。所以如何用 generator 来解决这个问题我就没再钻研。

我在 ruby-fiber 的异步事件处理 API 大概如下:

[pre lang="ruby"]button.on('click') do |info, helper|
  p info
  helper.wait(3)
  p 'wait 3sec'
end[/pre]

这里的 wait 内部就有个 fiber.yield。除此之外很有一些异步处理的 helper 方法(times、filter 之类)。原理大都是内部放个 fiber.yield,在必要的时候将这个回调函数挂起。

如果用 js-FRP 的话,API 差不多这样:

[pre lang="javascript"]button.on('click', function(info){ console.log(info) }).wait(3, function(info){ console.log('wait 3sec') })[/pre]

这里的 on、wait 返回的都是一个 eventStream。

感觉两者没多大差别。而且用 FRP 的话也不需要麻烦的每帧迭代了(wait 的话直接用 setTimeout)。
附庸的附庸不是我的附庸,女儿的女儿还是我的女儿。CK2沉迷ing
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部