WHITELEAF:Kindle応援サイト

KindleでWEB小説を読もう! Narou.rb 公開中

Ruby で構造体もどき

Ruby から離れていたのでリハビリがてらにちょろっとコードを書いてみた。
バイナリデータを扱う場合に unpack は使いづらい、だがしかし Gem の BinData とかを使うほど大げさでもない場合用。

構造体もどきを定義して名前でアクセスできる、が、やっぱり使いづらい('ω` ) 配列を扱えないので非常に無駄な部分が多発しそうです。配列対応すれば使えなくもないかもしれない。unpack のラッパーなので速度的にはこれが一番出ると思います。BinData はまさかの1バイトずつ読み込みでちょ〜っと速度低下がきになるレベル。

Ruby ぽいプログラムにはできたと思います。たぶん。

(PMD ファイルフォーマットは こちら を参考にしました)

(2/19 更新)
inherited を使うように変更

module BinStruct
  class UnknownPattern < StandardError; end
  class UnsupportTemplate < StandardError; end
  class InvalidStruct < StandardError; end

  class Base < BasicObject
    TEMPLATE_BYTES = {
      "a" => 1, "A" => 1, "Z" => 1, "h" => 2, "H" => 2, "c" => 1, "C" => 1,
      "s" => 2, "S" => 2, "i" => 4, "I" => 4, "l" => 4, "L" => 4, "q" => 8,
      "Q" => 8, "n" => 2, "N" => 2, "v" => 2, "V" => 2, "f" => 4, "d" => 8,
      "e" => 4, "E" => 4, "g" => 4, "G" => 8, "x" => 1
    }

    @@index_table = {}
    @@pattern = {}
    @@size = {}
    @@index = {}

    class << self
      def inherited(klass)
        @@index_table[klass] = {}
        @@pattern[klass] = ""
        @@size[klass] = 0
        @@index[klass] = 0
      end

      def method_missing(name, pattern)
        @@index_table[self][name] = @@index[self]
        @@index[self] += 1
        @@pattern[self] += pattern
        unless /(\w!?)(\d*)/ =~ pattern
          raise UnknownPattern, "unkonwn pattern '#{pattern}'"
        end
        template = $1
        length = $2.to_i
        length = 1 if length == 0
        bytes = TEMPLATE_BYTES[template]
        unless bytes
          raise UnsupportTemplate, "'#{template}' is not supported"
        end
        @@size[self] += bytes * length
        module_eval <<-EOS
          def #{name}
            return @values[#{@@index_table[self][name]}]
          end
        EOS
      end

      def __pattern__
        return @@pattern[self]
      end

      def __size__
        return @@size[self]
      end
    end

    def initialize(values)
      @values = values
    end
  end

  class Stream
    def initialize(file_name = nil)
      open(file_name) if file_name
    end

    def open(file_name)
      @buffer = File.read(file_name, File.size(file_name))
      @pos = 0
    end

    def read(struct_class)
      unless struct_class.superclass == Base
        raise InvalidStruct, "invalid struct class"
      end
      pattern = struct_class.__pattern__
      struct = struct_class.new(@buffer.unpack("@#{@pos}#{pattern}"))
      @pos += struct_class.__size__
      return struct
    end

    def pos=(position)
      @pos = position
    end
  end
end

class PMD
  class Header < BinStruct::Base
    magic "a3"
    version "f"
    model_name "Z20"
    comment "Z256"
    vert_count "L"
  end

  class TVertex < BinStruct::Base
    x "f"; y "f"; z "f"
    nx "f"; ny "f"; nz "f"
    u "f"; v "f"
    bone_num_1 "S"; bone_num_2 "S"
    bone_weight "C"
    edge_flag "C"
  end

  def test
    bin = BinStruct::Stream.new("normal.pmd")
    header = bin.read(Header)
    puts header.magic
    puts header.version
    puts header.model_name
    puts header.comment
    puts header.vert_count
    t_vertex = bin.read(TVertex)
    puts t_vertex.x
    puts t_vertex.y
    puts t_vertex.z
    puts t_vertex.nx
    puts t_vertex.ny
    puts t_vertex.nz
    puts t_vertex.u
    puts t_vertex.v
    puts t_vertex.bone_num_1
    puts t_vertex.bone_num_2
    puts t_vertex.bone_weight
    puts t_vertex.edge_flag
  end
end

pmd = PMD.new
pmd.test
Pmd
1.0
LatミクVer2.3N
Lat式ミクVer2.3 Normalモデル
Ver.20100627
エッジは「0.3〜0.6」くらいがオススメです

モデリング&データ変換 :Lat
Copyright	:CRYPTON FUTURE MEDIA, INC
15847
-0.10337000340223312
16.221189498901367
-0.6946200132369995
-0.9994639158248901
0.025453666225075722
0.020592058077454567
0.84170001745224
0.18515999615192413
33
33
100
0