7

グラフィック API を Lua に公開する Love2d Lua ゲーム エンジンを使用しています。ゲーム ワールドのすべてのセーブ データを含む巨大なハッシュ テーブルをシリアル化しようとしています。このハッシュにはいくつかの関数が含まれており、これらの関数のいくつかは Love2d C 関数を呼び出します。

ハッシュ内の関数をシリアル化するために、string.dump を使用し、loadstring でそれらをロードし直します。これは純粋な Lua 関数ではうまく機能しますが、Love2d API のようなラップされた C 関数を呼び出す関数をシリアル化してロードバックしようとすると、loadstring は nil を返します。

Love2d のグラフィック エンジンを介して画面に「hello, world」を描画する次の簡単なプログラムを考えてみましょう。

function love.load()
    draw = function()
        love.graphics.print('hello, world', 10, 10)
    end
end
function love.draw()
    draw()
end

これを実現したいと考えています:

function love.load()
    draw_before_serialize = function()
        love.graphics.print('hello, world', 10, 10)
    end

    out = io.open("serialized.lua", "wb")
    out:write('draw = load([[' .. string.dump(draw_before_serialize) .. ']])')
    out:close()

    require "serialized"
end
function love.draw()
    draw()
end

これを行うと、コンパイルされていない Lua と Lua バイトコードが混在するディスク上の Lua ファイルに書き込みます。これは次のようになります。

draw = load([[^[LJ^A^@      
       @main.lua2^@^@^B^@^B^@^D^E^B^B4^@^@^@%^A^A^@>^@^B^AG^@^A^@^Qhello, world 
       print^A^A^A^B^@^@]])

この方法は、C モジュールを呼び出さない Lua 関数でうまく機能します。この例は機能するため、これが問題であると考えられます。

function love.load()
    draw_before_serialize = function()
        print('hello, world')
    end

    out = io.open("serialized.lua", "wb")
    out:write('draw = load([[' .. string.dump(draw_before_serialize) .. ']])')
    out:close()

    require "serialized"
end
function love.draw()
    draw()
end

Love2d グラフィック メソッドを呼び出す代わりに、コンソールに出力します。

さらにテストを重ねた結果、この例が機能することがわかり、混乱しました。

function love.load()
    draw_before_serialize = function()
        love.graphics.print('hello, world', 10, 10)
    end

    draw = load(string.dump(draw_before_serialize))
end
function love.draw()
    draw()
end

ここでは、実際に関数をディスクに書き出すのではなく、単にダンプしてからすぐにロードし直します。おそらく犯人はバイナリ書き込みモード フラグを設定してデータを書き出していなかったのではないかと考えました ( "wb") が、Linux を使用しているため、このフラグは効果がありません。

何か案は?

4

1 に答える 1

6

問題は文字列のフォーマットにあると思います。Nicol Bolas は、バイトコード ダンプを囲む [[]] 引用符については正しいかもしれませんが、これはより大きな問題を示しています。バイトコードは実際には何でもかまいませんが、テキストファイルに読み書きできる通常の文字列のように扱っています。この問題は、ダンプされた文字列をファイルに書き込むことなくロードする最後のデモで実証されています。

関数を含むテーブルのシリアライザーのこの実装は、あなたが望むことを行うと思いますが、壊れているとも思います(まあ、とにかく正しく動作させることができませんでした...)。とにかく、それは正しい軌道に乗っています。バイトコードをフォーマットしてからファイル書き込む必要があります。

それを行うためのより良い方法があると確信していますが、これは機能します:

1.    binary = string.dump(some_function)
2.    formatted_binary = ""
3.    for i = 1, string.len(binary) do
4.        dec, _ = ("\\%3d"):format(binary:sub(i, i):byte()):gsub(' ', '0')
5.        formatted_binary = formatted_binary .. dec
6.    end

これは、バイトコードの各文字をループし、エスケープされたバイトとしてフォーマットします (それぞれは、"\097" のようなコードを含む文字列で、補間時に "a" にエスケープされます)。

このサンプルの 4 行目はやや密集しているので、分割します。初め、

binary:sub(i, i)

文字列から i 番目の文字を取り出します。それで

binary:sub(i, i):byte()

i 番目の文字の ASCII 整数表現を返します。次に、それをフォーマットします

("\\%3d"):format(binary:sub(i, i):byte())

たとえば、文字が「a」の場合、「\ 97」のような文字列が得られます。しかし、"\097" が必要なため、これは適切にエスケープされないため、gsub で " " を "0" に置き換えます。gsub は、結果の文字列と実行された置換の数を返すので、最初の戻り値を取得して「dec」に入れます。「%3d」形式がデフォルトでスペースを「0」に置き換えない理由がわかりません...まあ。

次に、フォーマットされたバイナリ文字列を実行するために、それをエスケープし、結果を "load" に渡す必要があります。Lua の変な [[]] 引用符は、"" のようなエスケープを行いません... 実際、それらがエスケープを行うかどうかはまったくわかりません。したがって、「some_function」にあるものを何でも実行する関数を返す実行可能な Lua 文字列を作成するには、次のようにします。

executable_string = 'load("' .. formatted_binary .. '")'

わかりました-すべてをまとめると、テストケースを次のように機能させることができると思います:

  1 function love.load()
  2     draw_before_serialize = function()
  3         love.graphics.print('hello, world', 10, 10)
  4     end
  5 
  6     binary = string.dump(draw_before_serialize)
  7     formatted_binary = ""
  8     for i = 1, string.len(binary) do
  9         dec, _ = ("\\%3d"):format(binary:sub(i, i):byte()):gsub(' ', '0')
 10         formatted_binary = formatted_binary .. dec
 11     end
 12     
 13     out = io.open("serialized.lua", "wb")
 14     out:write('draw = load("' .. formatted_binary .. '")')
 15     out:close()
 16     
 17     require "serialized"
 18 end 
 19 function love.draw()
 20     draw()
 21 end

これを Love で実行すると、隅に「hello world」が印刷された OpenGL 画面が表示されます。結果のファイル「serialized.lua」には、次のものが含まれます。

draw = load("\027\076\074\001\000\009\064\109\097\105\110\046\108\117\097\084\000\000\004\000\004\000\008\009\002\002\052\000\000\000\055\000\001\000\055\000\002\000\037\001\003\000\039\002\010\000\039\003\010\000\062\000\004\001\071\000\001\000\017\104\101\108\108\111\044\032\119\111\114\108\100\010\112\114\105\110\116\013\103\114\097\112\104\105\099\115\009\108\111\118\101\001\001\001\001\001\001\001\002\000\000")
于 2012-06-08T22:24:31.487 に答える