微妙なコマンドDSL

夜勤だったのだが暇すぎてボケーっとしている間に思いついたもの。

class Sprite
  @@command_hash = {}

  def self.define_command(name, &block)
    @@command_hash[name] = @@command_temp = []
    mod = Module.new
    def mod.method_missing(m, *arg)
      @@command_temp.push([m] + arg)
    end
    mod.module_eval(&block) if block
    include mod
  end
end

class Enemy < Sprite
  define_command(:command1) {
    def fire(angle, speed)
      puts "fire! angle=#{angle}, speed=#{speed}"
    end
    def wait(time)
      puts "wait! time=#{time}"
    end
    fire(0, 5)
    wait(2)
  }

  def initialize(name)
    @command = @@command_hash[name]
    @count = 0
  end

  def update
    self.__send__ @command[@count][0], *@command[@count][1..-1]
    @count += 1
  end
end

temp = Enemy.new(:command1)
temp.update # => fire! angle=0, speed=5
temp.update # => wait! time=2

define_commandメソッドで名無しモジュールをnewして、そのコンテキストでコマンドDSLを実行し、クラスにincludeする。
こうすることで、コマンドDSL内でのメソッド定義がそのクラスへの定義になる。
正確には名無しモジュールに定義してクラスへincludeすることで、DSL内のメソッド定義をそのブロック内に留めず、実行時に呼び出して評価できるようにする。こういう手を使わないとDSL内のメソッド定義はブロック実行時のみとなって、静的に実行・評価されてしまう。


このところLuaが組み込みスクリプトとして使われることが多くなったのは、実行時評価のスクリプトがアプリケーションの柔軟性を高めるためだ。
ゲームを作る場合でも、組み込みの関数を呼び出すだけの単純なスクリプトでは表現力に乏しいため、実行時に計算式を評価できるレベルのスクリプトが求められていると思う。
アクションゲームの場合はマイクロスレッドが使えると非常にラクになるが、RubyのFiberは大変遅いため、俺がとりあえずの目標としている弾幕STGでは使えない。
で、考えたのがコマンド列の実行だったのだが、そうすると今度は実行時評価ができなくなって、そもそもRubyというスクリプト言語を使う利点がゴソっと削れる。
無論、コマンドをメソッドとしてクラスに定義しておけば実行時評価もできるが、コマンド列とクラスが強く結びついてしまうと、コマンド列化する意味は薄れる。
コマンドの中に実行時評価される何かを記述することができればなー、と考えていたのだ。


まあ、これでゲームが簡単に作れる!とかいうような話では無いし、実際に作ってみてあんまり意味ないかも?という感じだったのでボツになりそうだ。
ひとつのアイデアと実装例ということで。