Miyako + irb で遊ぶ

Ruby関西#18にてサイロス誠さんと「Miyako + irb」についてお話をしたことは昨日の日記に書いたとおりです。
ここでは、実際に遊んでみたときのノートを残しておきます。

Miyakoのインストール

MiyakoはRuby/SDLのインストールが必須になります。ボク自身は下記の順番にインストールしました。

  1. ActiveScriptRubyのインストール
  2. MyGame(Windows用のRuby/SDL同梱版)のインストール
    • http://dgames.jp/ja/projects/mygame/
    • 上記HPにあるように、zipを展開して、install_mygame.rb をダブルクリックするだけで、自動的にRuby/SDLがインストールされます。
    • 複数のRubyがインストールされている場合には、それだけというわけにもいきませんが・・・
  3. Miyakoのインストール
    • http://www.twin.ne.jp/~cyross/Miyako/
    • Miyako_v1.0_with_rskit.zip をダウンロードしてみると、少し使い勝手が悪そうでしたので、Miyako_v1.0.zip を使いました。
    • インストール方法は、Miyako_v1.0.zip を展開し、miyako.rb, miyako_ext.rb を RUBY_ROOT\lib\ruby\site_ruby\1.8 に配置するだけです。
    • Miyakoのドキュメントは、個人が作成したとはにわかに信じられないほど充実していますので、他の展開済ファイルもディレクトリごとどこか判りやすい場所に置いておくと良さそうです。
    • irb上で、下記コマンドを実行し、trueが返ってきたら、インストール成功です。

irb(main):001:0> require 'miyako'

Miyakoを試す

まず、irbを起動し、先ほどと同じように

require 'miyako'

を実行します。このとき、"SDL_app"というWindowが生成されるのが判ります。
次に、Miyakoのサンプルから sample\anime\anime_sample.rb をエディタで開きます。
内容を見てみると、Miyakoでは

  1. Miyakoモジュールのインスタンス変数を作成し、
  2. Miyako.update, Miyako::Input.update, Miyako::Screen.update を実行することで、
  3. 画面の描画内容を更新

しているようです。

とりあえず、Miyakoモジュール内で作業が閉じていることが判りましたので、
下記のステートメントを実行します。

  irb(main):002:0> irb Miyako
  irb#1(Miyako):001:0>

慣れないと不思議かも知れませんが、これだけでMiyakoモジュール内に
入れたことになります。以下のような実験をしてみましょう。

  irb#1(Miyako):002:0> instance_variables
  => []
  irb#1(Miyako):003:0> @hoge = nil
  => nil
  irb#1(Miyako):004:0> instance_variables
  => ["@hoge"]
  irb#1(Miyako):005:0> exit
  => #<IRB::Irb: @signal_status=:IN_EVAL, @scanner=#<RubyLex:0x2930010>, @context=#<IRB::Context:0x290d30>>
  irb(main):003:0> Miyako.instance_variables
  => ["@hoge"]
  irb(main):004:0> irb Miyako
  irb#1(Miyako):001:0>

これは、「Miyakoモジュール内でインスタンス変数を@hogeを作成し、Miyakoモジュール外から
Miyakoモジュール内のインスタンス変数@hogeの存在を確認」しています。
モジュール内のインスタンス変数のため、Miyako内でクラスメソッドを用意すると、このhogeにアクセスできます。

  irb#1(Miyako):001:0> def self.show_hoge
  irb#1(Miyako):002:1>   @hoge
  irb#1(Miyako):003:1> end
  => nil
  irb#1(Miyako):004:0> exit
  => #<IRB::Irb: @signal_status=:IN_EVAL, @scanner=#<RubyLex:0x2930010>, @context=#<IRB::Context:0x2930d30>>
  irb(main):011:0> Miyako.show_hoge
  => nil
  irb(main):012:0>

次に、先ほどのanime_sample.rbを見ていきます。
8行目に"module Miyako" と書かれておりますので、9行目以降を
irbで実行すれば、期待として同じように動作するはずです。
1行ずつ貼り付けていくと、こんな感じで実行されていきます。

  irb#1(Miyako):002:0>   w = 0.1
  => 0.1
  irb#1(Miyako):003:0>
  irb#1(Miyako):004:0*   @spr1 = Sprite.new("anim_sprite_01.png")
  => #<Miyako::Sprite:0x2e54064 ...>
  irb#1(Miyako):005:0>   @spr1.oh = @spr1.ow
  => 32
  irb#1(Miyako):006:0>
  irb#1(Miyako):007:0*   @as1 = SpriteAnimation.new(@spr1, w)
  => #<Miyako::SpriteAnimation:0x2e4dc00 ...>
  irb#1(Miyako):008:0>   @as1.show
  => true
  irb#1(Miyako):009:0>   @as1.start
  => true
  irb#1(Miyako):010:0>
  irb#1(Miyako):011:0*   @spr2 = Sprite.new("anim_sprite_02.png")
  => #<Miyako::Sprite:0x2e46810 ...>
  irb#1(Miyako):012:0>   @spr2.ow = 32
  => 32
  irb#1(Miyako):013:0>   @spr2.oh = 32
  => 32
  irb#1(Miyako):014:0>   @spr2.y  = 32
  => 32
  irb#1(Miyako):015:0>
  irb#1(Miyako):016:0*   @as2 = SpriteAnimation.new(@spr2, w)
  => #<Miyako::SpriteAnimation:0x2e3c8b0 ...>
  irb#1(Miyako):017:0>   @as2.show
  => true
  irb#1(Miyako):018:0>   @as2.start
  => true
  irb#1(Miyako):019:0>

ここまでは画面に変化はありませんね。Miyako.updateを実行しなくては"SDL_app"の画面はいつまでも真っ黒なまま(むしろそれ以下)の状態です。

ここで、Miyako.updateを実行してみます。

  irb#1(Miyako):020:0* update
  => nil

一瞬、更新されるようですが、その後すぐに反応しなくなってしまうようです。

次に、anime_sample.rb の27行目からのloop処理ですが、この処理を
クラスメソッドとして定義します。

  irb#1(Miyako):028:0> def self.go_loop
  irb#1(Miyako):029:1>   loop do
  irb#1(Miyako):030:2*     Input.update
  irb#1(Miyako):031:2>
  irb#1(Miyako):032:2*     if Input.quit? || Input.pushed_any?(:esc)
  irb#1(Miyako):033:3>       break
  irb#1(Miyako):034:3>     end
  irb#1(Miyako):035:2>     if Input.pushed_any?(:btn1)
  irb#1(Miyako):036:3>       @as1.toggle_visible
  irb#1(Miyako):037:3>       @as2.toggle_visible
  irb#1(Miyako):038:3>     end
  irb#1(Miyako):039:2>     if Input.pushed_any?(:btn2)
  irb#1(Miyako):040:3>       @as1.toggle_exec
  irb#1(Miyako):041:3>       @as2.toggle_exec
  irb#1(Miyako):042:3>     end
  irb#1(Miyako):043:2>     @as2.move_character(Input.pushedAmount[0])
  irb#1(Miyako):044:2>     @as1.update
  irb#1(Miyako):045:2>     @as2.update
  irb#1(Miyako):046:2>     Screen.update
  irb#1(Miyako):047:2>   end
  irb#1(Miyako):048:1> end
  => nil
  irb#1(Miyako):049:0> go_loop

無事にアプリケーションとして動作していることを確認できるでしょう。
:btn1, :btn2, :esc は、それぞれキーボード入力の z,x,ESC を意味しています。

Miyakoとirbをもっと近づける

さて、ここからが本題です。
irbから動作中のSDLアプリケーションをリアルタイムに動かして
みたいものです。スレッドを使って遊んでみましょう。

  irb#1(Miyako):051:0> t = Thread.new{ go_loop }
  => #<Thread:0x2edad6c run>
  irb#1(Miyako):052:0> t.join
  IRB::Abort: abort then interrupt!!
          from C:/usr/ruby-1.8/lib/ruby/1.8/irb.rb:81
  irb#1(Miyako):053:0> t.join 5
  => nil
  irb#1(Miyako):054:0>

052行目では、スレッドをtに無理矢理変更し、それをCtrl+Cで強制停止させています。
ここで、SDLアプリケーション上で、ESCキーを入力してしまうと、go_loopの処理が
終了し、スレッドtが死んでしまいますので、注意が必要です。
053行目では、5秒間のlimitを与えて、スレッドをtに変更しています。

さて、この状態で、

  irb#1(Miyako):056:0> sleep 5

としてやると、メインスレッドは5秒間休止状態となり、その間実行待ち状態だったスレッドtが実行されます。
5秒間が経過すると、SDLアプリケーション側は休止し、irb側がキー入力待ち(実行状態)になります。
ここで、irb上にてBackSpaceキーなどを連打すると、やっぱりSDLアプリケーションが動くことが観測されます。
要するに、キーアサインを受け付けたタイミングで、メインスレッドが実行状態から実行可能状態に遷移し、スレッドtが実行可能→実行→実行可能状態に遷移しているようです。
当たり前のこととはいえ、実物を目の当たりにすると感慨深いモノです。(^^;

最後に、下記のようなコードを書いて実験してみました。

  # anime_sample_ext.rb
  
  module Miyako
    def self.u(&block)
      begin
        yield
        update
      end
    end
    
    def self.go_loop1
      _go_loop 1
    end
    
    def self.go_loop2
      _go_loop 2
    end
    
    def self._go_loop(n)
      loop do
        Input.update
  
        if Input.quit? || Input.pushed_any?(:esc)
          break
        end
        if Input.pushed_any?(:btn1)
          @as1.toggle_visible if n == 1
          @as2.toggle_visible if n == 2
        end
        if Input.pushed_any?(:btn2)
          @as1.toggle_exec if n == 1
          @as2.toggle_exec if n == 2
        end
        @as2.move_character(Input.pushedAmount[0]) if n == 2
        @as1.update if n == 1
        @as2.update if n == 2
        Screen.update
      end
    end
    
  end # Miyako

Miyako.u はブロックの中身を全部評価した後で、Miyako.updateを実行することを保証するだけです。
この状態で、2つのスレッドを同時に扱ってみます。

  irb#1(Miyako):059:0> Thread.kill(t)
  => #<Thread:0x2edad6c dead>
  irb#1(Miyako):060:0> Thread.list
  => [#<Thread:0x294c74c sleep>, #<Thread:0x2e5e690 run>]
  
  irb#1(Miyako):102:0* t1 = Thread.new{ go_loop1 }
  => #<Thread:0x2e7b268 run>
  irb#1(Miyako):103:0> t2 = Thread.new{ go_loop2 }
  => #<Thread:0x2e7609c run>
  irb#1(Miyako):104:0> Thread.list
  => [#<Thread:0x2e7609c run>, #<Thread:0x2e7b268 run>, #<Thread:0x294c74c sleep>, #<Thread:0x2e5e690  run>]
  irb#1(Miyako):105:0> sleep 5
  => 5

sleepしているスレッドは、irb(main)のものです。現在はirb#1(Miyako)がrunの状態にあります。
スレッドt1,t2に分けて実行してみると、先ほどのスレッドtの時よりも高速にアニメーションしているような気がしますが・・・(^^;
この状態では、

  t1.join
  t2.join
  [t1,t2].each{|tt| tt.join}

のいずれを実行しても、t1,t2が交互に実行されます。メインスレッドであるはずのirbには来ないのか?と考えてみると、Ctrl+cでInterruptできていることが、実行状態にある何よりの証明であることに気づく次第です。

とりあえず、これだけやればインパクトのあるプレゼンをirbだけで実現できるのかな?と思います。
それ以上のことをしたいのであれば、dRuby とかサーバーとかを立てて、外部からMiyako〜SDLに命令を投げることになるでしょう。