Question

J'ai entendu des marmonnements autour des internets sur le fait de pouvoir échanger du code à chaud dans Lua, comme cela se fait en Java, Erlang, Lisp, etc. Cependant, 30 minutes de recherche sur Google n'ont rien révélé.Quelqu'un a-t-il lu quelque chose de substantiel à ce sujet?Quelqu'un a-t-il une expérience en la matière?Fonctionne-t-il dans LuaJIT ou uniquement dans la VM de référence?

Je suis plus intéressé par la technique en tant que raccourci dans le développement / débogage que par un chemin de mise à niveau dans un environnement en direct.

Était-ce utile?

La solution

Lua, et la plupart des langages de script d'ailleurs, ne supportent pas la forme la plus généralisée de "remplacement à chaud" telle que vous la définissez. Autrement dit, vous ne pouvez pas avec garantie modifier un fichier sur le disque et faire en sorte que ses modifications se propagent dans un programme en cours d’exécution.

Cependant, Lua, et la plupart des langages de script d'ailleurs, sont parfaitement capables de formes contrôlées d'échange à chaud. Les fonctions globales sont des fonctions globales. Les modules chargent simplement les fonctions globales (si vous les utilisez de cette façon). Donc, si un module charge des fonctions globales, vous pouvez recharger le module s'il est modifié, et ces références de fonction globales changeront pour les fonctions nouvellement chargées.

Cependant , Lua, et la plupart des langages de script d'ailleurs, ne donnent aucune garantie à ce sujet. Tout ce qui se passe, c'est le changement des données d'état global. Si quelqu'un a copié une ancienne fonction dans une variable locale, il peut toujours y accéder. Si votre module utilise des données d'état local, la nouvelle version du module ne peut pas accéder à l'état de l'ancien module. Si un module crée une sorte d'objet qui a des fonctions membres, à moins que ces membres ne soient extraits des globaux, ces objets feront toujours référence aux anciennes fonctions, pas aux nouvelles. Et ainsi de suite.

De plus, Lua n'est pas thread-safe; vous ne pouvez pas simplement interrompre un lua_State à un moment donné et essayer de charger à nouveau un module. Vous devrez donc définir un moment précis pour qu'il vérifie des éléments et recharge les fichiers modifiés.

Vous pouvez donc le faire, mais ce n'est pas "pris en charge" dans le sens où cela peut arriver. Vous devez travailler pour cela, et vous devez faire attention à la façon dont vous écrivez les choses et à ce que vous mettez dans les fonctions locales par rapport aux fonctions globales.

Autres conseils

Comme l'a dit Nicol , le langage lui-même ne le fait pas pour vous.

Si vous voulez implémenter quelque chose comme ça vous-même, ce n'est pas si difficile, la seule chose qui vous "empêche" est les références "restantes" (qui pointeront toujours vers l'ancien code) et le fait require met en cache sa valeur de retour dans package.loaded.

La façon dont je le ferais est de diviser votre code en 3 modules:

  • la logique de rechargement au point d'entrée (main.lua)
  • toutes les données que vous souhaitez conserver lors des recharges (data.lua)
  • le code réel à recharger (payload.lua), en veillant à ne conserver aucune référence à cela (ce qui n'est parfois pas possible lorsque, par exemple, vous devez donner des rappels à une bibliothèque; voir ci-dessous).
-- main.lua:
local PL = require("payload")
local D = require("data")

function reload(module)
  package.loaded[module]=nil -- this makes `require` forget about its cache
  return require(module)
end

PL.setX(5)
PL.setY(10)

PL.printX()
PL.printY()

-- .... somehow detect you want to reload:
print "reloading"
PL = reload("payload") -- make sure you don't keep references to PL elsewhere, e.g. as a function upvalue!

PL.printX()
PL.printY()
-- data.lua:
return {} -- this is a pretty dumb module, it's literally just a table stored in `package.loaded.data` to make sure everyone gets the same instance when requiring it.
-- payload.lua:
local D = require("data")
local y = 0
return {
  setX = function(nx) D.x = nx end, -- using the data module is preserved
  setY = function(ny) y = ny end, -- using a local is reset upon reload
  printX = function() print("x:",D.x) end,
  printY = function() print("y:", y) end
}

sortie:

x: 5
y: 10
reloading
x: 5
y: 0

vous pourriez étoffer un peu mieux cette logique en ayant un "module de registre" qui garde une trace de tous les besoins / recharges pour vous et résume tout accès aux modules (vous permettant ainsi de remplacer les références), et, en utilisant le __index métatable sur ce registre, vous pouvez le rendre à peu près transparent sans avoir à appeler des getters laids partout. cela signifie également que vous pouvez fournir des rappels "one-liner" qui ne font alors qu'un appel final via le registre, si une bibliothèque tierce en a besoin.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top