Guida [C++]Upgrade da Lua 5.0 a Lua 5.3

Stato
Discussione chiusa ad ulteriori risposte.

HDueO

Utente Bronze
2 Agosto 2016
19
4
22
37
Ultima modifica da un moderatore:
[AWESOME][/AWESOME]Buondì,

giusto l'altro ieri, dopo l'ennesimo crash dovuto al malfunzionamento del garbage collector di Lua 5.0 (è del 2004 o giù di lì, non c'è da meravigliarsi se crasha) ho deciso di fare l'upgrade a Lua 5.3.3 (ultima versione di quest'anno).
Dato che ho altro da sbrigare, arrivo al dunque mostrando anche a voi come poter fare l'upgrade.
La guida sarà divisa in 4 parti: lib. Lua, server c++, makefile, server lua.

P1: Libreria Lua
Link del download ufficiale: https://www.lua.org/download.html
La libreria appena scaricata di per se va bene, solo che per ragioni di retrocompatibilità ci conviene fare delle modifiche. Per comodità vi mostrerò gli screen di Winmerge con le differenze evidenziate tra il prima ed il dopo le modifiche. A destra troverete il file prima delle modifiche, a sinistra troverete il file dopo le modifiche, in giallo/giallino è evidenziato il codice aggiunto mentre in nero/grigiastro il codice rimosso.
  • lauxlib.h:
    2qiuywk.png
    C++:
    /*
    ** Compatibility macros and functions
    */
    
    LUALIB_API int  lua_dofile (lua_State *L, const char *filename);
    LUALIB_API int  lua_dostring (lua_State *L, const char *str);
    LUALIB_API int  lua_dobuffer (lua_State *L, const char *buff,
      size_t sz, const char *n);
  • lauxlib.c:
    28coe0z.png
    C++:
    /*
    ** {======================================================
    ** compatibility code
    ** =======================================================
    */
    
    
    static void callalert (lua_State *L, int status) {
      if (status != 0) {
      lua_getglobal(L, "_ALERT");
      if (lua_isfunction(L, -1)) {
      lua_insert(L, -2);
      lua_call(L, 1, 0);
      }
      else {  /* no _ALERT function; print it on stderr */
      fprintf(stderr, "%s\n", lua_tostring(L, -2));
      lua_pop(L, 2);  /* remove error message and _ALERT */
      }
      }
    }
    
    
    static int aux_do (lua_State *L, int status) {
      if (status == 0) {  /* parse OK? */
      status = lua_pcall(L, 0, LUA_MULTRET, 0);  /* call main */
      }
      callalert(L, status);
      return status;
    }
    
    
    LUALIB_API int lua_dofile (lua_State *L, const char *filename) {
      return aux_do(L, luaL_loadfile(L, filename));
    }
    
    
    LUALIB_API int lua_dobuffer (lua_State *L, const char *buff, size_t size,
      const char *name) {
      return aux_do(L, luaL_loadbuffer(L, buff, size, name));
    }
    
    
    LUALIB_API int lua_dostring (lua_State *L, const char *str) {
      return lua_dobuffer(L, str, strlen(str), str);
    }
    
    /* }====================================================== */
  • ltablib.c (aggiunta funzione table.getn per mantenere retro-compatibilità):
    r85c1s.png
    C++:
    static int luaB_getn (lua_State *L) {
      lua_pushnumber(L, (lua_Number)aux_getn(L, 1, TAB_R));
      return 1;
    }
    
    /* }====================================================== */
    
    
    static const luaL_Reg tab_funcs[] = {
      {"concat", tconcat},
    #if defined(LUA_COMPAT_MAXN)
      {"maxn", maxn},
    #endif
      {"getn", luaB_getn}, // MODDED - backward compatibility
      {"insert", tinsert},
      {"pack", pack},
      {"unpack", unpack},
      {"remove", tremove},
      {"move", tmove},
      {"sort", sort},
      {NULL, NULL}
    };
Cancellate i vecchi file in ./liblua e caricate i nuovi file modificati.


P2: Server C++
Dovete sostituire in tutti i file:
  • "luaL_reg" con "luaL_Reg"
  • "luaL_getn" con "lua_objlen"
  • "lua_tonumber" con "lua_tointeger" (tranne che nella funzione "combine_lua_string" all'interno di questlua.cpp)
  • "lua_pushnumber" con "lua_pushinteger"
Per comodità vi mostrerò gli screen di Winmerge con le differenze evidenziate tra il prima ed il dopo le modifiche. A destra troverete il file prima delle modifiche, a sinistra troverete il file dopo le modifiche, in giallo/giallino è evidenziato il codice aggiunto mentre in nero/grigiastro il codice rimosso.
  • questlua.cpp:
    jkjf28.png
    C++:
         L = luaL_newstate();
    
         luaL_openlibs(L); // LUA 5.3
    2jcy4wk.png
    C++:
         lua_gc(L,LUA_GCCOLLECT,0); // launch the garbage collector
         lua_gc(L,LUA_GCSTEP,0); // init garbage collector step counter
    2mxkepk.png
    C++:
       QuestState CQuestManager::OpenState(const string& quest_name, int state_index)
       {
         QuestState qs;
         qs.args=0;
         qs.st = state_index;
         qs.co = lua_newthread(L);
         qs.ico = luaL_ref(L, LUA_REGISTRYINDEX);
         return qs;
       }
    
       //
       // * RunState
       //
       // decides script to wait for user input, or finish
       //
       bool CQuestManager::RunState(QuestState & qs)
       {
         ClearError();
    
         m_CurrentRunningState = &qs;
         int ret = lua_resume(qs.co, L, qs.args);
    
         if (ret == LUA_OK || ret == LUA_YIELD)
    2re61za.png
    C++:
       void CQuestManager::CloseState(QuestState& qs)
       {
         if (qs.co)
         {
           qs.co = NULL;
           luaL_unref(L, LUA_REGISTRYINDEX, qs.ico);
           lua_gc(L,LUA_GCSTEP,1); // increase garbace collector step counter
         }
       }
  • questmanager.cpp (importante bugfix della cache degli script lua, veniva usato come indice unico un indirizzo di memoria castato a signed long int da 32 bit anche quando la macchina è a 64 bit e quindi con indirizzi da 64 bit):
    34646ir.png
    C++:
           lua_getglobal(qs.co, "__codecache"); // stack : __codecache
           lua_pushinteger(qs.co, (unsigned long long)code); // stack : __codecache (codeptr)
           lua_rawget(qs.co, -2); // stack : __codecache (compiled-code)
           if (lua_isnil(qs.co, -1)) // cache miss
           { // load code to lua, save it to cache and only function remain in stack
             lua_pop(qs.co, 1); // stack : __codecache
             luaL_loadbuffer(qs.co, code, code_size, quest_name.c_str()); // stack : __codecache (compiled-code)
             lua_pushinteger(qs.co, (unsigned long long)code); // stack : __codecache (compiled-code) (codeptr)
             lua_pushvalue(qs.co, -2); // stack : __codecache (compiled-code) (codeptr) (compiled_code)
             lua_rawset(qs.co, -4); // stack : __codecache (compiled-code)
             lua_remove(qs.co, -2); // stack : (compiled-code)
           }
           else // cache hit
           {
             lua_remove(qs.co, -2); // stack : (compiled-code)
           }
Sostituite il file "lua_incl.h" con
C++:
#define LUA_COMPAT_5_1
#if !defined(_MSC_VER) && defined(__cplusplus)
extern "C" {
#endif

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <luaconf.h>

#if !defined(_MSC_VER) && defined(__cplusplus)
}
#endif

Vi consiglio di editare/riscrivere il comando "do_priv_guild" presente in "cmd_gm.cpp".

P3: Makefile
Per compilare Lua dovrete entrare nella cartella ./liblua e lanciare il comando "make freebsd && make local".
Per compilare i file server dovrete editare il Makefile aggiungendo i nuovi link alla libreria lua sostituendo tutte le ricorrenze della stringa "/liblua/" con "/liblua/install/", successivamente lanciate il comando "gmake clean && gmake dep && gmake tag && gmake".

P4: Server Lua
Dovete sostituire in tutti i file la funzione "math.mod" con la funzione "math.fmod".
Dovete sostituire in tutti i file il comando "!=" con il comando "~=" (cosa che dovrebbe fare in automatico il qc).
Rimuovete da tutti i file lua le funzioni deprecate "table.foreach" e "table.foreachi", un modo intelligente per farlo è il seguente
Codice:
--Da
table.foreachi(tab,function(index,value) say( "tab["..index.."] = "..value ) end)
--A
for index, value in ipairs(tab) do say( "tab["..index.."] = "..value ) end
--Da
table.foreach(tab,function(key,value) say( "tab['"..key.."'] = "..value ) end)
--A
for key, value in pairs(tab) do say( "tab['"..key.."'] = "..value ) end

Andate nel file "questlib.lua" e sostituite la funzione
Codice:
function select(...)
  return q.yield('select', arg)
end
con
Codice:
function select(...)
  return q.yield('select', {...})
end

Se non ho dimenticato qualcosa dovrebbe essere tutto.
L'upgrade a Lua 5.3 sembra abbastanza stabile, attualmente dopo le ultime modifiche non ho riscontrato alcun crash dovuto al garbage collector e 12 anni di aggiornamenti Lua si fanno sentire nelle nuove perfomance.
Non chiedetemi di farvi un nuovo qc (quest compilator), non lo uso, non mi serve e sono impegnato con altri progetti.


- Edited (23/08/2016) -
Riscritta la funzione CQuestManager::RunState (file questlua.cpp) aggiungendo:
  • gestione delle coroutine morte
  • gestione di eventuali overflow degli argomenti sulla pila lua
- Edited (01/09/2016) -
Dato che la funzione "lua_resume" dovrebbe essere usata solo su coroutine "sospese", ho dovuto aggiungere la gestione dei vari casi in cui una coroutine non sia sospesa. Ho aggiornato di conseguenza la funzione "CQuestManager::RunState".
Senza questa patch è possibile far volontariamente crashare l'intero core, quindi l'aggiornamento è critico (se non ho capito male, il bug è presente anche con la versione 5.0 di lua).
C++:
bool CQuestManager::RunState(QuestState & qs)
    {
        ClearError();
        m_CurrentRunningState = &qs;
     
        if (!lua_checkstack(qs.co, qs.args))
        {
            // lua_pop(qs.co, qs.args);  /* remove results anyway */
            sys_err("LUA_ERROR: too many arguments to resume.");
            WriteRunningStateToSyserr();
            SetError();
            GotoEndState(qs);
            return false;
        }

        switch (lua_status(qs.co))
        {
            case LUA_OK:
                {
                    lua_Debug ar;
                    if (lua_getstack(qs.co, 0, &ar) > 0) // normal
                    {
                        sys_err("LUA_ERROR: cannot resume non-suspended coroutine.");
                        WriteRunningStateToSyserr();
                        SetError();
                        GotoEndState(qs);
                        return false;
                    }
                    else if (lua_gettop(qs.co) == 0) // dead
                    {
                        sys_err("LUA_ERROR: cannot resume dead coroutine.");
                        WriteRunningStateToSyserr();
                        SetError();
                        GotoEndState(qs);
                        return false;
                    }
                    // else is suspended
                }
                break;
            case LUA_YIELD:
                break;
            default:
                {
                    sys_err("LUA_ERROR: cannot resume dead coroutine.");
                    WriteRunningStateToSyserr();
                    SetError();
                    GotoEndState(qs);
                    return false;
                }
                break;
        }

        int status = lua_resume(qs.co, NULL, qs.args); // resumes only suspended coroutines
        if (status != LUA_OK && status != LUA_YIELD)
        {
            sys_err("LUA_ERROR: %s, %s, %d", lua_tostring(qs.co, 1), lua_tostring(qs.co, -1), status);
            WriteRunningStateToSyserr();
            SetError();
            GotoEndState(qs);
            return false;
        }
     
        int nres = lua_gettop(qs.co);
        if (!lua_checkstack(qs.co, nres + 1))
        {
            lua_pop(qs.co, nres);  /* remove results anyway */
            sys_err("LUA_ERROR: too many results to resume %d", status);
            WriteRunningStateToSyserr();
            SetError();
            GotoEndState(qs);
            return false;
        }
     
        if ( status == LUA_OK && nres == 0) // dead coroutine
        {
            // end of quest
            GotoEndState(qs);
            return false;
        }

        string state = lua_tostring(qs.co, 1);
        if ( state == "select" )
        {
            GotoSelectState(qs);
            return true;
        }
        else if ( state == "wait" )
        {
            GotoPauseState(qs);
            return true;
        }
        else if ( state == "input" )
        {
            GotoInputState(qs);
            return true;
        }
        else if ( state == "confirm" )
        {
            GotoConfirmState(qs);
            return true;
        }
        else if ( state == "select_item" )
        {
            GotoSelectItemState(qs);
            return true;
        }
     
        sys_err("LUA_ERROR: unknown state %s", state.c_str());
        WriteRunningStateToSyserr();
        SetError();
        GotoEndState(qs);
        return false;
    }



Altri progetti? Stiamo sostituendo il motore grafico, passeremo da Granny ad Unreal Engine 4. A tal proposito cerchiamo gente volenterosa che ci aiuti nell'impresa C++ (che verrà pubblicata in questo forum coi dovuti crediti agli sviluppatori).

Commenti: "Riusciremo finalmente a liberarci una volta per tutte dall'incubo degli exporter gr2!!!"
Saluti,

Acqua
 
Altri progetti? Stiamo sostituendo il motore grafico, passeremo da Granny ad Unreal Engine 4. A tal proposito cerchiamo gente volenterosa che ci aiuti nell'impresa C++ (che verrà pubblicata in questo forum coi dovuti crediti agli sviluppatori).

Commenti: "Riusciremo finalmente a liberarci una volta per tutte dall'incubo degli exporter gr2!!!"
Saluti,

Acqua
"e anche a liberarvi di tutta la gente che sfrutta un pc vecchio per giocare* :evvai:
Comunque, sarebbe una gran cosa questa. Si porterebbe ad un miglioramento del gioco mai pensato su Metin2 e chi lo sa, magari potrebbe essere la rinascita di questo gioco grazie al nuovo aspetto.

Vi consiglio di creare una discussione in "Progetti e Idee" riguardo questo upgrade.
Seguo il progetto!
 
  • Mi piace
Reazioni: xDAMIANO
Un informazione, se metin da granny passa a unreal engine 4 dopo anche le prestazioni del pc dovranno essere più alte per giocare?
 
Secondo me non ha senso fare l'upgrade.
Complimenti per la guida, ma secondo me, oltre a riscrivere gli script lua con le funzioni, crea numervoli bug e non aumenta nessuna prestazione
 
Secondo me non ha senso fare l'upgrade.
Complimenti per la guida, ma secondo me, oltre a riscrivere gli script lua con le funzioni, crea numervoli bug e non aumenta nessuna prestazione
Le prestazioni aumentano, stiamo parlando di un grabage collector del 2004 (quello di Lua 5.0.3) contro un garbage collector del 2016 (quello di Lua 5.3.3)! Gli script non sono da rifare, basta levare i foreach (tutto scritto nella guida). I bug ci sono anche adesso, ci vuole "poco" far crashare un server M2 con lua 5.0.3...

Un informazione, se metin da granny passa a unreal engine 4 dopo anche le prestazioni del pc dovranno essere più alte per giocare?
Non per forza, dipende dalla grafica adottata e dalle funzionalità che si vogliano usare.

P.S.
Ho riscritto la funzione RunState aggiungendo la gestione delle coroutine morte e di eventuali overflow degli argomenti sulla pila lua. 5 minuti ed aggiorno il topic principale.
 
Onestamente sti bug li hai solo tu, avrai leggermente sminchiato tutto ahahah.
Non ho mai avuto nessun bug del genere dal lontano 2010
 
Secondo me non ha senso fare l'upgrade.
Complimenti per la guida, ma secondo me, oltre a riscrivere gli script lua con le funzioni, crea numervoli bug e non aumenta nessuna prestazione
Scusa, stai dicendo serio?!

E' un upgrade fantastico, per chi ama scriptare in maniera pulita ed ordinata è una vera chicca
 
Ah, quasi dimenticavo: dovete anche sostituire in tutti i file il comando "!=" con il comando "~=" (cosa che dovrebbe fare in automatico il qc). -> Aggiornato.
 
Novità

Dato che la funzione "lua_resume" dovrebbe essere usata solo su coroutine "sospese", ho dovuto aggiungere la gestione dei vari casi in cui una coroutine non sia sospesa. Ho aggiornato di conseguenza la funzione "CQuestManager::RunState". Senza questa patch è possibile far volontariamente crashare l'intero core, quindi l'aggiornamento è critico (se non ho capito male, il bug è presente anche con la versione 5.0 di lua).
C++:
bool CQuestManager::RunState(QuestState & qs)
    {
        ClearError();
        m_CurrentRunningState = &qs;
      
        if (!lua_checkstack(qs.co, qs.args))
        {
            // lua_pop(qs.co, qs.args);  /* remove results anyway */
            sys_err("LUA_ERROR: too many arguments to resume.");
            WriteRunningStateToSyserr();
            SetError();
            GotoEndState(qs);
            return false;
        }

        switch (lua_status(qs.co))
        {
            case LUA_OK:
                {
                    lua_Debug ar;
                    if (lua_getstack(qs.co, 0, &ar) > 0) // normal
                    {
                        sys_err("LUA_ERROR: cannot resume non-suspended coroutine.");
                        WriteRunningStateToSyserr();
                        SetError();
                        GotoEndState(qs);
                        return false;
                    }
                    else if (lua_gettop(qs.co) == 0) // dead
                    {
                        sys_err("LUA_ERROR: cannot resume dead coroutine.");
                        WriteRunningStateToSyserr();
                        SetError();
                        GotoEndState(qs);
                        return false;
                    }
                    // else is suspended
                }
                break;
            case LUA_YIELD:
                break;
            default:
                {
                    sys_err("LUA_ERROR: cannot resume dead coroutine.");
                    WriteRunningStateToSyserr();
                    SetError();
                    GotoEndState(qs);
                    return false;
                }
                break;
        }

        int status = lua_resume(qs.co, NULL, qs.args); // resumes only suspended coroutines
        if (status != LUA_OK && status != LUA_YIELD)
        {
            sys_err("LUA_ERROR: %s, %s, %d", lua_tostring(qs.co, 1), lua_tostring(qs.co, -1), status);
            WriteRunningStateToSyserr();
            SetError();
            GotoEndState(qs);
            return false;
        }
      
        int nres = lua_gettop(qs.co);
        if (!lua_checkstack(qs.co, nres + 1))
        {
            lua_pop(qs.co, nres);  /* remove results anyway */
            sys_err("LUA_ERROR: too many results to resume %d", status);
            WriteRunningStateToSyserr();
            SetError();
            GotoEndState(qs);
            return false;
        }
      
        if ( status == LUA_OK && nres == 0) // dead coroutine
        {
            // end of quest
            GotoEndState(qs);
            return false;
        }

        string state = lua_tostring(qs.co, 1);
        if ( state == "select" )
        {
            GotoSelectState(qs);
            return true;
        }
        else if ( state == "wait" )
        {
            GotoPauseState(qs);
            return true;
        }
        else if ( state == "input" )
        {
            GotoInputState(qs);
            return true;
        }
        else if ( state == "confirm" )
        {
            GotoConfirmState(qs);
            return true;
        }
        else if ( state == "select_item" )
        {
            GotoSelectItemState(qs);
            return true;
        }
      
        sys_err("LUA_ERROR: unknown state %s", state.c_str());
        WriteRunningStateToSyserr();
        SetError();
        GotoEndState(qs);
        return false;
    }

P.S. Aggiornato di conseguenza anche il topic principale.
 
  • Mi piace
Reazioni: luca5997
Nulla contro la vostra voglia di migliorare, anzi vi appoggio... Ma UE4 non sembra esagerato come GE?? Io se fossi in te farei piccoli passi piuttosto che passi da gigante e poi fare errori come metin2mester.
 
Ottima guida, ho utilizzato alcune parti per passare da lua 5.1 a 5.3.4.
Vorrei solo segnalare/comunicare che io ho avuto problemi con la funzione lua_objlen che, nonostante sia definita come macro nel file "luaconf.h" nel seguente modo
C++:
#define lua_objlen(L,i)        lua_rawlen(L, (i))
in fase di compilazione mi dava errore.
Risolto molto semplicemente sostituendo direttamente "lua_rowlen" a tutte le occorrenze di "luaL_getn"

Un altro piccolo problema ricontrato a causa di qc è relativo alla "nuova" sintassi dei cicli for e while (non so bene quando sia stata introdotta) che richiede necessariamente la parola chiave "do" al posto della parola chiave "begin"; in altre parole il qc in mio possesso sostituisce tutti i token "do" nel codice lua con un token "begin", esempio:
Codice:
while ( cont <= 8 ) do
viene compilato da qc generando il seguente codice nell'object
Codice:
while ( cont <= 8 ) begin
evidentemente questa sintassi non è più tollerata (prima le quest funzionavano) generando un errore, di facile risoluzione, nel syserr del mio core. Sono in possesso di alcuni sorgenti di qc ma credo siano molto datati e non relativi al qc che utilizzo, vedrò cosa posso fare.
Un'altra cosa che non è più tollerata è il "!" come negazione, ora è strettamente necessaria la parola chiave "not".
Ultimo "problema" riscontrato è relativo ad alcune sequenze di caratteri coreani nel file "locale.lua": alcune sequenze di escape vengono interpretate malamente. Anche questo facilmente risolvibile eliminando i caratteri "\" inappropriati o traducendo le stringhe che causano problemi
 
Quanto conviene, effettivamente, fare questo upgrade?
A livello teorico abbastanza, il Garbage Collector (principale causa di crash) è stato revisionato molto nelle versioni, ma il lavoro da fare è abbastanza, soprattutto se non si è mai fatto. Inoltre nel mio caso è necessario rifare/aggiornare il Quest Compiler ed è necessario modificare un po' la libreria di Lua per creare le API necessarie a poter compilare il QC.
Inoltre io sto avendo problemi con una di queste che è stata rivista e non capisco come lavora, per di più la funzione a cui mi riferisco non nasce come API ma come funzione interna quindi non c'è praticamente documentazione...


Quando faremo entrare utenti in questo test server ti farò sapere se il gioco vale effettivamente la candela ma io credo di si, lua 5.0 ha parecchi anni...
 
Di sicuro male non fa, ma è inutile se poi si va incontro ad errori e problemi che, per chi come me è inesperto, sono di difficile risoluzione.
Per tutti quelli che invece sanno dove mettere mano allora il discorso cambia.
 
Stato
Discussione chiusa ad ulteriori risposte.