Un projet en plusieurs fichiers
Découper un gros programme en un fichier principal et des bibliothèques avec import, lire un fichier de réglages avec des valeurs par défaut, tenir un journal, et mettre le programme à jour sur beaucoup d'ordinateurs par disquette ou par le réseau.
Passé une centaine de lignes, un programme d'un seul fichier devient difficile à parcourir, et les mêmes aides (dessiner un bouton, lire un réglage, écrire un journal) finissent copiées d'un programme à l'autre. Cette recette construit un petit panneau de commande comme on construit les gros programmes : un startup court, un fichier principal, et des bibliothèques dans leur propre dossier, chargées avec import. Puis elle répond à la question qui vient avec le succès : comment installer une nouvelle version sur les douze ordinateurs qui le font tourner.
Le panneau lui-même est volontairement simple : un bouton par sortie de redstone, nommée dans un fichier de réglages. C'est la structure qui compte ; elle convient à toutes les recettes de ce livre.
Le projet
/startup une ligne : import "panneau/main"
/panneau/main le programme : réglages, boutons, boucle principale
/panneau/lib/config lit un fichier de réglages "clé = valeur", avec des valeurs par défaut
/panneau/lib/ui aides au dessin : une barre de titre, des boutons, une ligne d'état
/panneau/lib/log un journal de lignes datées, gardé petit
/panneau/lib/update installe une nouvelle version reçue par le réseau
/panneau.cfg les réglages de CET ordinateur (hors du programme)
/panneau.log ce qui s'est passé, écrit par lib/logDeux choix rendent tout le reste facile :
- Le programme vit dans son propre dossier. Le copier, le mettre à jour ou le supprimer, c'est un seul dossier ; d'autres programmes peuvent vivre à côté.
- Les réglages vivent en dehors. Chaque ordinateur garde son titre et ses sorties dans
/panneau.cfg, et une nouvelle version de/panneaupeut remplacer tous les fichiers du programme sans y toucher.
Un Micro-ordinateur lui va bien (couleurs, disquette, port réseau pour les mises à jour). Tapez edit panneau pour ouvrir tout le dossier dans l'éditeur, avec son arborescence à gauche : chaque fichier est à un clic.
Comment marche import
import "chemin" exécute un autre fichier et rend ce que ce fichier renvoie. Les règles, toutes visibles dans l'exemple ci-dessous :
- Le chemin est relatif au dossier du fichier qui importe : dans
/boutique/main,import "lib/prix"charge/boutique/lib/prix, et dans/boutique/lib/prix,import "monnaie"charge/boutique/lib/monnaie, son voisin. Un chemin qui commence par/part de la racine. À l'invite (et dans les exemples de cette page), un chemin relatif part du dossier courant. - Un fichier ne s'exécute qu'une fois par programme. L'importer de nouveau, d'où que ce soit, rend la même valeur sans le relancer :
monnaieci-dessous n'affiche « chargement de monnaie » qu'une fois, bien que deux fichiers l'importent. Tous les fichiers qui importent une bibliothèque partagent la même table, et le même état. - **Les variables
localdu fichier restent à lui ; ses globales sont vues partout.** Une bibliothèque déclare donc une tablelocal M = {}, y range ses fonctions, et finit parreturn M.
-- trois petits fichiers, écrits ici pour que l'exemple tourne n'importe où
fs.write("/boutique/lib/monnaie", 'print("chargement de monnaie")\nreturn {ecrire = function(n) return n .. " pièces" end}')
fs.write("/boutique/lib/prix", 'local monnaie = import "monnaie"\nreturn {fer = monnaie.ecrire(4)}')
fs.write("/boutique/main", 'local prix = import "lib/prix"\nlocal monnaie = import "lib/monnaie"\nprint("fer : " .. prix.fer)\nprint(monnaie.ecrire(12))')
import "/boutique/main"
local r = pcall(import, "lib/or")
print(r.error)chargement de monnaie fer : 4 pièces 12 pièces snippet:6: cannot import 'lib/or': no such file
require est la même fonction, pour qui vient de ComputerCraft. Les autres erreurs que vous pourriez croiser :
| erreur | cause |
|---|---|
cannot import 'lib/uii': no such file | une faute de frappe dans le chemin, ou un chemin relatif à un autre dossier que celui que vous croyez |
cannot import 'lib': it is a folder | le chemin désigne un dossier, pas un fichier |
circular import: 'panneau/main' is still being imported | deux fichiers s'importent l'un l'autre : déplacez ce qu'ils partagent dans un troisième |
panneau/lib/ui:12: ... | une erreur dans le fichier importé : le message donne ce fichier et sa ligne |
Seul import est relatif au fichier. Les chemins donnés à fs sont relatifs au dossier courant : celui du shell quand le programme a été lancé, la racine au démarrage. Dans /panneau/main, fs.read("panneau.cfg") chercherait /panneau.cfg au démarrage, mais /panneau/panneau.cfg si on le lance à la main depuis /panneau. Écrivez les chemins de fs depuis la racine, avec un /, comme ce projet.
Les bibliothèques suivent la règle de Brass sur les fonctions : une fonction ne peut pas utiliser une local de la fonction qui l'entoure. L'état d'une bibliothèque (le chemin du journal, par exemple) est donc gardé dans des variables local du fichier lui-même, que toutes ses fonctions peuvent lire et modifier.
Les fichiers
startup
Une seule ligne : le démarrage lance startup, qui passe la main au programme.
-- startup : lancé au démarrage. Tout le programme vit dans le dossier panneau.
import "panneau/main"panneau/main
Le programme lui-même se lit comme une table des matières : il importe ses bibliothèques, charge les réglages avec leurs valeurs par défaut, crée un bouton par face nommée dans les réglages, et attend les évènements. Un clic commute une sortie et écrit une ligne au journal ; un message part vers update.traiter, qui ignore tout sauf les nouvelles versions.
-- panneau/main : un panneau de commande. Chaque face nommée dans /panneau.cfg reçoit
-- un bouton qui commute sa sortie de redstone. Lancé par /startup.
local config = import "lib/config"
local ui = import "lib/ui"
local log = import "lib/log"
local update = import "lib/update"
local VERSION = 3
local reglages = config.charger("/panneau.cfg", {
titre = "PANNEAU DE COMMANDE",
journal = "/panneau.log",
taille_journal = 4000,
})
log.regler(reglages.journal, reglages.taille_journal)
-- Un bouton par face qui a un nom dans les réglages : left = Fours
local boutons = {}
for _, face in ipairs(rs.sides()) do
if type(reglages[face]) == "string" then
table.insert(boutons, {x = 3, y = 3 + #boutons * 2, l = 26, texte = reglages[face], face = face, actif = false})
end
end
local function dessiner()
ui.effacer()
ui.entete(reglages.titre .. " v" .. VERSION)
for _, b in ipairs(boutons) do ui.bouton(b) end
if #boutons == 0 then
ui.etat("Ajoutez 'left = Lampes' à /panneau.cfg", term.colors.orange)
else
ui.etat("Cliquez un bouton. Journal : " .. reglages.journal)
end
end
log.info("panneau v" .. VERSION .. " démarré")
dessiner()
while true do
local e = os.pull_event()
if e.name == "click" then
for _, b in ipairs(boutons) do
if ui.touche(b, e) then
b.actif = not b.actif
rs.set(b.face, b.actif)
ui.bouton(b)
log.info(b.texte .. (b.actif and " en marche" or " à l'arrêt"))
end
end
elseif e.name == "message" then
update.traiter(e)
end
endrs.sides() donne les six faces : les réglages peuvent nommer n'importe laquelle. Sa valeur est vérifiée avec type(...) == "string" : une ligne comme left = 3 donnerait un nombre, pas le texte d'un bouton.
panneau/lib/config
Le fichier de réglages est du texte simple que le joueur modifie avec edit /panneau.cfg. analyser le lit ligne par ligne : il saute les lignes vides, les commentaires (#) et les lignes sans =, coupe chaque ligne au premier =, enlève les espaces, et convertit les nombres et true/false. charger complète le résultat avec les valeurs par défaut, et écrit un premier fichier fait de ces valeurs quand il n'y en a pas, pour que le joueur ait de quoi partir.
-- panneau/lib/config : lit un fichier de réglages fait de lignes "clé = valeur".
-- Les lignes qui commencent par # sont des commentaires ; nombres et true/false sont convertis.
local M = {}
-- "12" donne 12, "true" donne true, tout le reste reste du texte
local function convertir(texte)
if texte == "true" then return true end
if texte == "false" then return false end
local n = tonumber(texte)
if n then return n end
return texte
end
-- Les réglages écrits dans un texte, sous forme de table
function M.analyser(texte)
local reglages = {}
for _, brute in ipairs(string.split(texte, "\n")) do
local ligne = string.trim(brute)
local egal = string.find(ligne, "=")
if egal and not string.starts(ligne, "#") then
local cle = string.trim(string.sub(ligne, 1, egal - 1))
reglages[cle] = convertir(string.trim(string.sub(ligne, egal + 1)))
end
end
return reglages
end
-- Écrit les réglages dans un fichier que le joueur peut modifier
function M.enregistrer(chemin, reglages)
local lignes = {"# Réglages : une ligne 'clé = valeur' par réglage"}
for k, v in pairs(reglages) do
table.insert(lignes, k .. " = " .. tostring(v))
end
fs.write(chemin, table.concat(lignes, "\n") .. "\n")
end
-- Les réglages de chemin, complétés par les valeurs par défaut. Un fichier absent est
-- créé à partir d'elles, pour qu'il y ait quelque chose à modifier.
function M.charger(chemin, defauts)
local texte = fs.read(chemin)
if texte == nil then
M.enregistrer(chemin, defauts)
texte = ""
end
local reglages = M.analyser(texte)
for k, v in pairs(defauts) do
if reglages[k] == nil then reglages[k] = v end
end
return reglages
end
return MM.charger appelle M.enregistrer, défini plus haut dans le fichier. Ce sont deux champs de M, cherchés au moment de l'appel : l'ordre des définitions n'aurait même pas d'importance.
Après un premier lancement, et trois lignes ajoutées à la main, /panneau.cfg contient :
# Réglages : une ligne 'clé = valeur' par réglage
titre = HALLE DE FONTE
journal = /panneau.log
taille_journal = 4000
left = Fours
right = Convoyeur
top = Lumièrespanneau/lib/ui
De petites fonctions de dessin pour un écran de texte, les mêmes dans tous les programmes : une barre de titre, un bouton qui sait s'il est en marche, un test pour les clics, une ligne d'état. Un bouton est une table : le programme peut y garder son état (actif), et M.touche compare un clic avec la position du bouton lui-même.
-- panneau/lib/ui : des aides au dessin pour les écrans de texte.
local M = {}
local couleurs = term.colors
function M.effacer()
term.set_bg(couleurs.black)
term.clear()
end
-- Une barre bleue en haut de l'écran
function M.entete(titre)
term.set_cursor(1, 1)
term.set_bg(couleurs.blue)
term.set_fg(couleurs.white)
term.write(" " .. titre .. string.rep(" ", term.get_size().w)) -- coupé au bord
term.set_bg(couleurs.black)
end
-- Un bouton est une table {x =, y =, l =, texte =, actif =} : vert en marche, rouge à l'arrêt
function M.bouton(b)
local etat = b.actif and "MARCHE" or "ARRÊT"
local texte = " " .. b.texte
term.set_cursor(b.x, b.y)
term.set_bg(b.actif and couleurs.green or couleurs.red)
term.set_fg(couleurs.white)
term.write(texte .. string.rep(" ", b.l - #texte - #etat - 1) .. etat .. " ")
term.set_bg(couleurs.black)
end
-- Le clic e est-il tombé sur le bouton ?
function M.touche(b, e)
return e.y == b.y and e.x >= b.x and e.x < b.x + b.l
end
-- Une ligne de texte en bas de l'écran
function M.etat(texte, couleur)
term.set_cursor(1, term.get_size().h)
term.set_bg(couleurs.black)
term.clear_line()
term.set_fg(couleur or couleurs.light_gray)
term.write(texte)
end
return Mpanneau/lib/log
Un journal répond à « qui a arrêté le convoyeur cette nuit ? ». Chaque ligne commence par l'heure du monde. Quand le fichier dépasse octets_max, il est renommé avec .old (à la place de l'ancien) et un nouveau commence : le journal ne prend jamais plus du double de sa taille sur le disque.
-- panneau/lib/log : un journal de lignes datées qui ne grossit jamais trop.
local M = {}
local chemin = "/programme.log" -- changé par M.regler
local octets_max = 4000
function M.regler(fichier, taille)
chemin, octets_max = fichier, taille
end
local function heure()
local t = (os.day_time() + 6000) % 24000
return string.format("%02d:%02d", t // 1000, t % 1000 * 60 // 1000)
end
local function ajouter(ligne)
local taille = fs.size(chemin)
if taille and taille > octets_max then
fs.move(chemin, chemin .. ".old") -- remplace l'ancien journal précédent
end
fs.append(chemin, ligne .. "\n")
end
-- Un disque plein ne doit pas arrêter le programme : la ligne est perdue, c'est tout
function M.ecrire(niveau, texte)
pcall(ajouter, heure() .. " " .. niveau .. " " .. texte)
end
function M.info(texte)
M.ecrire("INFO", texte)
end
function M.alerte(texte)
M.ecrire("ALERTE", texte)
end
return MM.regler change chemin et octets_max, deux variables local du fichier : toutes les fonctions de la bibliothèque voient les nouvelles valeurs. Et comme une bibliothèque ne s'exécute qu'une fois, lib/update, qui importe log lui aussi, écrit dans le fichier choisi par panneau/main.
L'écriture passe par pcall : un disque plein fait perdre une ligne de journal plutôt que d'arrêter le panneau.
panneau/lib/update
Elle attend un message sur le canal maj qui porte le mot secret et une table de fichiers, écrit chaque fichier, répond, et redémarre pour que la nouvelle version parte du début. La réponse part vers l'expéditeur avant le redémarrage : net.send remet le message tout de suite, et os.reboot ne prend effet qu'à la fin du tick.
-- panneau/lib/update : installe une nouvelle version du programme envoyée par "publier".
local log = import "log" -- panneau/lib/log : le même module que celui de main
local M = {}
local SECRET = "atelier-laiton-7"
local CANAL = "maj"
local function installer(fichiers)
for chemin, texte in pairs(fichiers) do
fs.write("/" .. chemin, texte)
end
end
-- Appelée par main pour chaque évènement "message" : true si c'était une nouvelle version
function M.traiter(e)
local d = e.data
if e.channel ~= CANAL or type(d) ~= "table" or d.secret ~= SECRET or type(d.fichiers) ~= "table" then
return false
end
local r = pcall(installer, d.fichiers)
if not r.ok then
log.alerte("mise à jour " .. tostring(d.version) .. " ratée : " .. r.error)
net.send(e.sender, {secret = SECRET, version = d.version, ok = false, erreur = r.error}, CANAL)
return true
end
log.info("mis à jour en version " .. tostring(d.version) .. " par n°" .. e.sender)
net.send(e.sender, {secret = SECRET, version = d.version, ok = true}, CANAL)
os.reboot() -- les nouveaux fichiers repartent du début
return true
end
return MSi l'écriture échoue en route (not enough space), certains fichiers sont neufs et d'autres anciens. La bibliothèque répond alors avec l'erreur au lieu de redémarrer : l'ancienne version continue de tourner en mémoire, et vous pouvez faire de la place et publier de nouveau.
À quoi ça ressemble
L'écran du panneau, dessiné par les fonctions de lib/ui (recopiées dans l'exemple, puisque cette page n'a pas de disque d'où importer) avec les trois sorties des réglages ci-dessus :
local M = {}
local couleurs = term.colors
function M.effacer()
term.set_bg(couleurs.black)
term.clear()
end
function M.entete(titre)
term.set_cursor(1, 1)
term.set_bg(couleurs.blue)
term.set_fg(couleurs.white)
term.write(" " .. titre .. string.rep(" ", term.get_size().w))
term.set_bg(couleurs.black)
end
function M.bouton(b)
local etat = b.actif and "MARCHE" or "ARRÊT"
local texte = " " .. b.texte
term.set_cursor(b.x, b.y)
term.set_bg(b.actif and couleurs.green or couleurs.red)
term.set_fg(couleurs.white)
term.write(texte .. string.rep(" ", b.l - #texte - #etat - 1) .. etat .. " ")
term.set_bg(couleurs.black)
end
function M.etat(texte, couleur)
term.set_cursor(1, term.get_size().h)
term.set_bg(couleurs.black)
term.clear_line()
term.set_fg(couleur or couleurs.light_gray)
term.write(texte)
end
local ui = M
ui.effacer()
ui.entete("HALLE DE FONTE v3")
ui.bouton({x = 3, y = 3, l = 26, texte = "Fours", actif = true})
ui.bouton({x = 3, y = 5, l = 26, texte = "Convoyeur", actif = false})
ui.bouton({x = 3, y = 7, l = 26, texte = "Lumières", actif = true})
ui.etat("Cliquez un bouton. Journal : /panneau.log")
Le tester
- Créez les dossiers et les fichiers :
mkdir panneau,mkdir panneau/lib, puisedit panneauet le bouton + Fichier pour chaque fichier (ouedit panneau/lib/uipour chacun). Écrivezstartupà la racine. - Redémarrez (Ctrl+R). Le premier lancement crée
/panneau.cfget vous invite à ajouter une sortie. Modifiez-le, ajoutezleft = Fours, redémarrez : le bouton apparaît. Cliquez-le : la sortie commute, et le journal le raconte :
> cat panneau.log 18:02 INFO panneau v3 démarré 18:03 INFO Fours en marche 18:05 INFO Fours à l'arrêt
- Faites une erreur exprès : renommez
panneau/lib/uienpanneau/lib/uiiavecmv. Le démarrage s'arrête surpanneau/main:4: cannot import 'lib/ui': no such file: le message donne le fichier et la ligne de l'import.
Mettre à jour beaucoup d'ordinateurs
Une fois le panneau installé dans chaque halle de l'usine, chaque correction doit atteindre chaque copie. Deux façons.
Par disquette : cloner
Les fichiers d'un ordinateur vivent sur son disque, et chaque ordinateur garde son disque dans son lecteur. cloner copie des fichiers d'un disque vers d'autres, sur un seul ordinateur. Il lit tout en mémoire tant que le disque d'origine est dans le lecteur, puis attend que vous changiez de disque : le programme continue de tourner quand son disque est éjecté, puisqu'il est déjà chargé, et un évènement disk lui dit quand un disque sort (e.inserted vaut false) et quand un autre entre (true). Il ne reste qu'à porter chaque nouveau disque à son ordinateur.
-- cloner : copie des fichiers et des dossiers sur d'autres disques.
-- Usage, depuis la racine : cloner startup panneau puis changez de disque quand il le demande.
if #arg == 0 then
print("Usage : cloner <fichier ou dossier> ...")
return
end
-- 1. Tout est lu en mémoire, tant que le disque d'origine est dans le lecteur
local fichiers = {}
local nombre, octets = 0, 0
local function rassembler(chemin)
if fs.is_dir(chemin) then
for _, nom in ipairs(fs.list(chemin)) do
rassembler(chemin .. "/" .. nom)
end
else
local texte = fs.read(chemin)
if texte == nil then error("pas de fichier '" .. chemin .. "'") end
fichiers[chemin] = texte
nombre, octets = nombre + 1, octets + #texte
end
end
for _, nom in ipairs(arg) do
rassembler(nom)
end
print(nombre .. " fichiers lus, " .. octets .. " octets.")
local function tout_ecrire()
for chemin, texte in pairs(fichiers) do
fs.write(chemin, texte)
end
end
-- 2. Un évènement "disk" arrive quand un disque quitte le lecteur et quand un autre y entre
while true do
print("Éjectez le disque (accroupi + clic droit main vide) et insérez-en un neuf. Ctrl+T pour arrêter.")
repeat
local e = os.pull_event("disk")
until e.inserted
local r = pcall(tout_ecrire)
if r.ok then
print("Copié. " .. fs.free() .. " octets libres sur ce disque.")
else
print("Échec : " .. r.error)
end
endLancez-le depuis la racine avec les noms à copier : cloner startup panneau. Il ne copie pas /panneau.cfg : chaque disque garde ses propres réglages. Un ordinateur n'accepte que les disques de son âge ou plus anciens (un Micro-ordinateur lit les disquettes), et le Microcontrôleur n'a pas de lecteur du tout : pour lui, passez par le réseau.
Par le réseau : publier et recevoir
L'ordinateur de l'atelier garde la copie de référence. publier rassemble startup et tous les fichiers de /panneau dans une table, trouve les ordinateurs dont l'étiquette commence par panneau (net.computers donne le label de chacun), et leur envoie la table avec net.send. Puis il attend leurs réponses et renvoie aux silencieux, trois fois au plus : le même protocole de réponses, de délais et de renvois que dans Télécommande par le réseau.
-- publier : envoie le programme à tous les ordinateurs dont l'étiquette commence par
-- "panneau", et attend la réponse de chacun. Usage : publier 4 (le numéro de version)
local SECRET = "atelier-laiton-7"
local CANAL = "maj"
local PREFIXE = "panneau" -- les étiquettes des ordinateurs à mettre à jour
local DELAI = 3 -- secondes d'attente des réponses
local ESSAIS = 3
local version = tonumber(arg[1])
if version == nil then
print("Usage : publier <numéro de version>")
return
end
-- Tous les fichiers d'un dossier et de ses sous-dossiers : fichiers["panneau/lib/ui"] = "..."
local function rassembler(dossier, fichiers)
for _, nom in ipairs(fs.list("/" .. dossier)) do
local chemin = dossier .. "/" .. nom
if fs.is_dir("/" .. chemin) then
rassembler(chemin, fichiers)
else
fichiers[chemin] = fs.read("/" .. chemin)
end
end
end
local fichiers = {startup = fs.read("/startup")}
rassembler("panneau", fichiers)
local taille = 0
for _, texte in pairs(fichiers) do taille = taille + #texte end
if taille > 30000 then error("le programme est trop gros pour un seul message : " .. taille .. " caractères") end
-- Les ordinateurs à mettre à jour, trouvés par leur étiquette
local cibles = {}
local nombre = 0
for _, c in ipairs(net.computers()) do
if c.label and string.starts(c.label, PREFIXE) then
cibles[c.id] = {etiquette = c.label, essais = 0, fini = false}
nombre = nombre + 1
end
end
print("Version " .. version .. ", " .. taille .. " caractères, vers " .. nombre .. " ordinateur(s)")
local message = {secret = SECRET, version = version, fichiers = fichiers}
-- Envoie à ceux qui n'ont pas répondu ; renvoie le nombre d'envois
local function envoyer_tour()
local envoyes = 0
for id, c in pairs(cibles) do
if not c.fini and c.essais < ESSAIS then
c.essais = c.essais + 1
net.send(id, message, CANAL)
envoyes = envoyes + 1
end
end
return envoyes
end
local function tous_finis()
for _, c in pairs(cibles) do
if not c.fini then return false end
end
return true
end
-- Une réponse : {secret =, version =, ok =, erreur =} venue de l'une des cibles
local function sur_reponse(e)
local d = e.data
if e.name ~= "message" or e.channel ~= CANAL or cibles[e.sender] == nil then return end
if type(d) ~= "table" or d.secret ~= SECRET or d.version ~= version then return end
local c = cibles[e.sender]
c.fini = true
if d.ok then
print("n°" .. e.sender .. " " .. c.etiquette .. " : à jour")
else
print("n°" .. e.sender .. " " .. c.etiquette .. " : échec, " .. tostring(d.erreur))
end
end
while envoyer_tour() > 0 do
local minuterie = os.start_timer(DELAI)
while not tous_finis() do
local e = os.pull_event()
if e.name == "timer" and e.id == minuterie then break end
sur_reponse(e)
end
end
for id, c in pairs(cibles) do
if not c.fini then print("n°" .. id .. " " .. c.etiquette .. " : pas de réponse") end
endUn message porte 32 768 caractères au plus (sinon message too large), d'où la vérification de taille : au-delà, envoyez un fichier par message. Un ordinateur qui reçoit un gros message doit aussi avoir de la place pour lui dans sa file d'évènements, qui peut prendre un quart de sa mémoire : environ 30 Ko de texte sur un Microcontrôleur, dont la mémoire ne contient de toute façon que 16 Ko.
Sur les ordinateurs qui font déjà tourner le panneau, lib/update reçoit la nouvelle version. Un ordinateur tout neuf n'a encore rien pour la recevoir : tapez une fois ce court programme recevoir, donnez une étiquette à l'ordinateur, et lancez-le.
-- recevoir : à taper une fois sur un nouvel ordinateur, après lui avoir donné une
-- étiquette (label panneau-4), puis lancez publier à l'atelier. Il installe et redémarre.
local SECRET = "atelier-laiton-7"
print("J'attends le programme, je suis le n°" .. net.id())
while true do
local m = net.receive()
local d = m.data
if m.channel == "maj" and type(d) == "table" and d.secret == SECRET and type(d.fichiers) == "table" then
for chemin, texte in pairs(d.fichiers) do
fs.write("/" .. chemin, texte)
end
net.send(m.sender, {secret = SECRET, version = d.version, ok = true}, "maj")
os.reboot()
end
end> label panneau-4 > recevoir J'attends le programme, je suis le n°23
Puis, à l'atelier :
> publier 4 Version 4, 6641 caractères, vers 4 ordinateur(s) n°23 panneau-4 : à jour n°12 panneau-1 : à jour n°15 panneau-2 : à jour n°17 panneau-3 : pas de réponse
Ici, panneau-3 n'a pas répondu trois fois : il est éteint, hors de portée, son chunk n'est pas chargé, ou son panneau ne tourne pas (un ordinateur revenu à l'invite du shell perd le message, alors que net.send renvoie true). Publiez de nouveau plus tard ; les autres installeront simplement la même version une fois de plus.
Qui connaît le mot secret peut installer n'importe quel programme sur vos ordinateurs. Gardez-le long, ne l'envoyez jamais avec net.broadcast, et changez-le en même temps dans publier, recevoir et lib/update.
Conseils pour les gros programmes
- **Gardez
maincourt.** Quand une partie grossit, déplacez-la dans une bibliothèque au nom qui dit ce qu'elle fait. Une bonne bibliothèque s'explique en une phrase. - Pas de globales. Une globale posée dans un fichier est vue par tous les autres, et deux bibliothèques qui utilisent toutes deux
comptes'écraseront l'une l'autre. Utilisezlocal, et renvoyez une table. - Un numéro de version affiché à l'écran (
v3dans la barre de titre) dit d'un coup d'œil quels ordinateurs sont à jour. - Testez une bibliothèque seule à l'invite
brass:ui = import "/panneau/lib/ui", puis appelez ses fonctions une à une. Chaque ligne de l'invite est un morceau à part : utilisez-y une globale, pas unelocal.