2012-06-02 16 views
6

Stiamo usando il motore di gioco Love2d Lua che espone una grafica a Lua. Stiamo cercando di serializzare una tabella hash gigante che contiene tutti i dati di salvataggio del gioco per il mondo di gioco. Questo hash include alcune funzioni e alcune di queste funzioni chiamano le funzioni di Love2d C.È possibile chiamare loadstring su una stringa di bytecode lua che contiene un riferimento a una funzione C?

Per serializzare le funzioni nell'hash, si utilizza string.dump e si caricano di nuovo con loadstring. Funziona bene per le funzioni Lua pure, ma quando proviamo a serializzare e quindi a caricare di nuovo una funzione che chiama una funzione C avvolta come quella nell'Api di Love2d, loadstring restituisce nil.

Si consideri il seguente programma semplice che disegna "ciao, mondo" alla schermata di via motore grafico di Love2d:

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

Vorremmo essere in grado di fare questo:

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 

Facendo questo scrive su un file Lua su disco che contiene un mix di codice bytec Lua e Lua non compilato, che assomiglia a questo:

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

Questo metodo funziona perfettamente con le funzioni Lua che non chiamano i moduli C. Noi pensiamo che questo è il problema, perché questo esempio funziona:

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 

Invece di chiamare il metodo grafico Love2d, lo fa una stampa alla console.

Dopo ulteriori test, siamo stati confusi per scoprire che questo esempio funziona:

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 

Qui in realtà non scriviamo la funzione sul disco, e invece solo discarica e poi subito a caricare di nuovo . Abbiamo pensato che forse il colpevole non stava scrivendo i dati con il set di flag in modalità scrittura binaria ("wb"), ma dato che siamo su Linux questo flag non ha alcun effetto.

Qualche idea?

+1

"fa una stampa alla console" cosa stampa? Inoltre, sei sicuro che l'ambiente globale utilizzato dal codice precedente sia lo stesso dell'ambiente globale utilizzato da 'require'? [dato che dipendi dal fatto che 'draw' sia definito nell'ambiente globale] – snogglethorpe

+1

Dovresti essere avvisato che:' [['.. string.dump (draw_before_serialize) ..']] 'non funzionerà necessariamente. Il dump che ottieni potrebbe contenere * qualsiasi cosa *, inclusi i caratteri ']]'. Ciò finirebbe presto la stringa, rompendo così le cose. –

+2

@NicolBolas Una volta ho visto una soluzione semplice ma intelligente per quello che ha appena controllato la stringa per '] (= *)]' e poi ho incorniciato il dump con uno '=' più del numero massimo di '=' 'trovato con la partita. – jpjacobs

risposta

5

Penso che il problema sia nella formattazione della stringa. Nicol Bolas potrebbe avere ragione circa le [[]] virgolette che circondano il dump del codice byte, ma questo indica un problema più grande; Il codice byte potrebbe essere qualsiasi cosa, ma lo stai trattando come se fosse una normale stringa che può essere scritta e letta da un file di testo. Questo problema è dimostrato dall'ultima demo, in cui si carica la stringa di dumping senza mai scriverla sul file.

This implementazione di un serializzatore per le tabelle che includono funzioni tipo fa quello che vuoi, penso, ma penso anche che sia rotto (beh, non ho potuto farlo funzionare correttamente comunque ...). Comunque è sulla strada giusta. È necessario formattare il bytecode e quindi scriverlo nel file.

Sono sicuro che c'è un modo migliore per farlo, ma questo funziona:

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 

Questo loop attraverso ogni carattere nel bytecode, li formatta come byte sfuggiti (ognuno è una stringa contenente un codice come "\ 097", che in caso di interpolazione scapperebbe in "a").

La riga 4 di questo esempio è un po 'densa, quindi la analizzerò.Primo,

binary:sub(i, i) 

estrae il carattere i'th dalla stringa. Quindi

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

restituisce la rappresentazione intera ascii del carattere i'th. Poi abbiamo formattarla con

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

che ci dà una stringa come "\ 97", ad esempio, se il personaggio fosse "a". Ma questo non sfuggirà correttamente perché abbiamo bisogno di "\ 097", quindi facciamo un gsub che sostituisce "" con "0". Gsub restituisce la stringa risultante e il numero di sostituzioni che sono state eseguite, quindi prendiamo il primo valore restituito e lo inseriamo in "dec". Non sono sicuro del motivo per cui il formato "% 3d" non sostituisce gli spazi con "0" di default ... vabbè.

Quindi per eseguire la stringa binaria formattata, è necessario evitarlo e passare il risultato a "caricare". Le virgolette [[]] di Lua non escono come "" ... in effetti non sono sicuro che facciano delle fughe. Allora per fare una stringa Lua eseguibile che restituirà una funzione che farà tutto ciò che è in "una_qualche_funzione", facciamo questo:

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

Ok - in modo da mettere tutto insieme, penso che possiamo fare il vostro test- caso di lavoro in questo modo:

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 

Quando eseguo questo con Amore ottengo uno schermo OpenGL con "ciao mondo" stampato in un angolo. Il file risultante "serialized.lua" contiene quanto segue:

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") 
+0

È difficile realizzare l'opos? Ho una stringa serializzata simile a quella che hai postato e voglio cambiarla in una lanquage umana per apportare alcune piccole modifiche allo script che questa stringa genera. –

Problemi correlati