Temps et minuteries
Les ticks, sleep et les minuteries, les horloges de l'ordinateur et l'heure du monde : attendre, mesurer, répéter, et agir à la tombée de la nuit.
Le temps est partout dans une machine : une lampe qui clignote, un écran rafraîchi chaque seconde, une chaîne de presses qui tourne 30 secondes, des réverbères qui s'allument à la tombée de la nuit. Brass a quelques outils pour cela : sleep pour attendre, les minuteries pour attendre tout en faisant autre chose, os.clock et os.time pour mesurer, et os.day_time pour lire l'heure du monde Minecraft.
Cette page explique comment ils comptent, lequel choisir, et ce que devient le temps quand l'ordinateur cesse de tourner ou que son chunk se décharge.
Les ticks
Minecraft avance par ticks : 20 par seconde quand le serveur suit, soit 0,05 seconde par tick. Un ordinateur s'exécute une fois par tick, et tous les outils de cette page comptent en ticks : sleep, les minuteries, os.time et os.clock.
Les durées se donnent en secondes (sleep(0.5), os.start_timer(2)) et se convertissent en ticks en arrondissant vers le haut. Un vingtième de seconde est le pas le plus fin : rien en Brass n'attend moins d'un tick.
Quand le serveur rame et fait moins de 20 ticks par seconde, les programmes ralentissent avec le reste du monde : un sleep(1) attend toujours 20 ticks, qui durent alors plus d'une vraie seconde. Les machines de Create ralentissent de la même façon, si bien qu'un programme reste au pas de la machine qu'il pilote.
sleep
sleep(secondes) met le programme en pause. C'est la façon la plus simple d'attendre, et la bonne pour les programmes qui font une chose après l'autre : une lampe qui clignote, une suite de pistons, le cycle d'un feu tricolore.
for _, voulu in ipairs({0, 0.01, 0.06, 0.5, 1}) do
local debut = os.time()
sleep(voulu)
print("sleep(" .. voulu .. ") : " .. os.time() - debut .. " tick(s)")
endsleep(0) : 1 tick(s) sleep(0.01) : 1 tick(s) sleep(0.06) : 2 tick(s) sleep(0.5) : 10 tick(s) sleep(1) : 20 tick(s)
sleep(0) et sleep(0.01) attendent tous deux un tick : c'est la pause la plus courte, pratique pour laisser le monde appliquer une sortie avant la suivante (voir Redstone et liaisons de Create). sleep(0.06) attend deux ticks, jamais moins que demandé.
Pendant qu'il dort, le programme n'exécute aucune instruction et ne traite aucun évènement : ceux qui arrivent attendent dans la file jusqu'à ce que le programme les lise (Évènements).
Les horloges de l'ordinateur : os.time et os.clock
os.time() est le nombre de ticks depuis le démarrage de l'ordinateur. os.clock() donne la même chose en secondes (les ticks divisés par 20) : il avance donc par pas de 0,05.
Elles servent à mesurer des durées : lisez l'horloge deux fois et faites la différence.
local debut = os.clock()
sleep(1.5)
print("attendu " .. os.clock() - debut .. " s")attendu 1.5 s
Trois choses à savoir sur ces horloges :
- Elles repartent de 0 à chaque démarrage : quand l'ordinateur est allumé, redémarré, ou chargé de nouveau avec son chunk. Leur valeur seule ne veut rien dire, seule une différence compte. Deux ordinateurs affichent des valeurs différentes au même instant.
- Elles comptent les ticks où l'ordinateur a vraiment tourné. Quand il s'arrête de tourner, ses horloges s'arrêtent aussi (voir
Temps et minuteries). - Elles n'ont rien à voir avec l'heure du monde : pour celle-ci, utilisez
os.day_time.
Mesurer une durée
Un Observateur intelligent surveille le tapis à la sortie d'une Presse mécanique : il s'allume chaque fois qu'un objet pressé passe, et son signal arrive sur la face gauche de l'ordinateur. Ce programme affiche le temps entre deux objets, la vraie cadence de la chaîne :
local precedent = nil
while true do
os.pull_event("redstone")
if rs.get("left") > 0 then -- le début d'une impulsion
local maintenant = os.clock()
if precedent ~= nil then
print(string.format("%.2f s depuis l'objet précédent", maintenant - precedent))
end
precedent = maintenant
end
endLa précision est d'un tick (0,05 s). Pour chronométrer quelque chose de plus court, répétez-le et divisez : mesurez 20 objets, pas un seul.
L'heure du monde : os.day_time
os.day_time() est l'heure du monde, de 0 à 23999. Une journée Minecraft dure 24000 ticks, soit 20 minutes, et 0 n'est pas minuit mais le lever du soleil :
os.day_time() | heure | dans le monde |
|---|---|---|
| 0 | 06:00 | lever du soleil |
| 1000 | 07:00 | plein jour |
| 6000 | 12:00 | midi |
| 12000 | 18:00 | début du coucher du soleil |
| 13000 | 19:00 | nuit : les monstres peuvent apparaître dehors |
| 18000 | 00:00 | minuit |
| 23000 | 05:00 | aube |
Une heure du monde vaut 1000 ticks (50 vraies secondes) : l'heure est donc t // 1000 en partant de 6 heures. Pour afficher l'heure comme une horloge :
local function texte_heure(t)
local heures = (t // 1000 + 6) % 24
local minutes = (t % 1000) * 60 // 1000
return string.format("%02d:%02d", heures, minutes)
end
print(texte_heure(0))
print(texte_heure(6000))
print(texte_heure(13000))
print(texte_heure(18000))
print(texte_heure(23500))06:00 12:00 19:00 00:00 05:30
os.day_time ne donne que l'heure du jour en cours, pas la date : le nombre de jours écoulés depuis la création du monde n'est pas disponible. Tenez votre propre compteur (dans un fichier, voir plus bas) si vous en avez besoin.
L'heure du monde peut sauter : quand les joueurs dorment pour passer la nuit, ou après une commande /time set. Avec la règle de jeu doDaylightCycle désactivée, elle ne bouge plus du tout. N'attendez jamais une valeur exacte (os.day_time() == 13000 n'est presque jamais vrai, puisque le programme ne lit l'heure que de temps en temps) : testez un intervalle, ou repérez qu'un moment est passé depuis la dernière lecture, comme le fait la routine quotidienne plus bas.
Minuterie ou sleep ?
os.start_timer(secondes) rend la main tout de suite avec un identifiant, et un évènement timer portant cet id rejoint la file quand le temps est écoulé. Son délai s'arrondit au tick supérieur comme sleep, avec un tick au minimum.
sleep(secondes) | os.start_timer(secondes) | |
|---|---|---|
| le programme | s'arrête jusqu'au bout | continue tout de suite |
| les évènements pendant ce temps | attendent dans la file | sont traités au fil de l'eau |
| la fin de l'attente | sleep rend la main | un évènement timer avec id |
| idéal pour | les séquences : clignoter, impulsions, cycles | les programmes qui réagissent : boutons, écrans, messages |
Un programme peut avoir 256 minuteries en attente au plus ; une de plus l'arrête avec too many timers. Une minuterie ne s'annule pas : oubliez son identifiant et ignorez son évènement. La section Évènements montre les usages courants : une minuterie périodique dans la boucle principale, et un délai d'attente.
Planifier
Toutes les 5 minutes. Dans une boucle principale, une minuterie relancée chaque fois qu'elle sonne :
local SAUVER_TOUTES = 5 * 60
local minuterie = os.start_timer(SAUVER_TOUTES)
while true do
local e = os.pull_event()
if e.name == "timer" and e.id == minuterie then
-- écrire les compteurs dans un fichier, envoyer un rapport...
minuterie = os.start_timer(SAUVER_TOUTES)
end
endQuand le travail lui-même prend du temps, ce planning dérive un peu : la minuterie suivante démarre après le travail. Quand plusieurs tâches tournent à des rythmes différents, une échéance pour chacune les garde séparées et ne dérive jamais :
local prochain_rapport = os.clock() + 300 -- toutes les 5 minutes
local prochain_releve = os.clock() + 2 -- toutes les 2 secondes
while true do
local maintenant = os.clock()
if maintenant >= prochain_releve then
-- lire le coffre renforcé, mettre l'écran à jour
prochain_releve = prochain_releve + 2
end
if maintenant >= prochain_rapport then
-- envoyer les chiffres à la salle de contrôle
prochain_rapport = prochain_rapport + 300
end
sleep(0.5)
endÀ la tombée de la nuit. Lisez l'heure toutes les quelques secondes et agissez quand le jour bascule en nuit, pas pendant toute la nuit :
local function fait_nuit()
local t = os.day_time()
return t >= 13000 and t < 23000
end
local nuit = nil -- inconnu au départ : la première lecture agit toujours
while true do
local nuit_maintenant = fait_nuit()
if nuit_maintenant ~= nuit then
nuit = nuit_maintenant
rs.set("top", nuit) -- les réverbères du village
end
sleep(5)
endLa recette Éclairage jour et nuit construit un éclairage complet sur cette idée.
Exemple : une horloge à l'écran
L'heure du monde en grands chiffres, rafraîchie chaque seconde, sur un ciel bleu le jour et sombre la nuit. L'écran ci-dessous est celui d'un ordinateur à midi.
local function texte_heure(t)
local heures = (t // 1000 + 6) % 24
local minutes = (t % 1000) * 60 // 1000
return string.format("%02d:%02d", heures, minutes)
end
-- les lettres de gfx font 3 pixels de large plus 1 d'espace, fois l'échelle
local function centre(y, texte, couleur, echelle)
local largeur = #texte * 4 * echelle - echelle
gfx.text((gfx.size().w - largeur) // 2 + 1, y, texte, couleur, echelle)
end
while true do
local t = os.day_time()
local nuit = t >= 13000 and t < 23000
gfx.clear(nuit and "blue" or "light_blue")
centre(50, texte_heure(t), "white", 12)
centre(130, nuit and "NUIT" or "JOUR", nuit and "yellow" or "white", 3)
sleep(1)
end
Pour afficher aussi l'heure sur un Panneau d'affichage ou un Tube Nixie de Create, ajoutez display.set({"Heure", texte_heure(t)}) dans la boucle et pointez une Liaison d'affichage sur l'ordinateur (display).
Exemple : une routine quotidienne
Un village Create qui vit au rythme de l'horloge : le moulin démarre au lever du soleil et s'arrête au coucher, une cloche sonne à midi, les réverbères s'allument la nuit. Chaque ligne de la routine est une heure et une sortie. Le moulin tourne à travers un Embrayage sur la face gauche : un embrayage alimenté l'arrête, donc le moulin démarre quand la sortie s'éteint.
local routine = {
{heure = 0, face = "left", allume = false}, -- 06:00 embrayage relâché : le moulin démarre
{heure = 6000, face = "right", allume = true}, -- 12:00 la cloche sonne
{heure = 6100, face = "right", allume = false}, -- et s'arrête
{heure = 12000, face = "left", allume = true}, -- 18:00 embrayage alimenté : le moulin s'arrête
{heure = 13000, face = "top", allume = true}, -- 19:00 réverbères allumés
{heure = 23000, face = "top", allume = false}, -- 05:00 réverbères éteints
}
-- le moment est-il passé entre deux lectures ? (la journée repasse de 23999 à 0)
local function passe(moment, de, vers)
if de <= vers then
return moment > de and moment <= vers
end
return moment > de or moment <= vers
end
local derniere = os.day_time()
while true do
sleep(1)
local maintenant = os.day_time()
for _, etape in ipairs(routine) do
if passe(etape.heure, derniere, maintenant) then
rs.set(etape.face, etape.allume)
end
end
derniere = maintenant
endComme elle regarde ce qui est passé depuis la dernière lecture, la routine résiste aux sauts : quand les joueurs dorment toute la nuit, chaque étape entre le soir et le matin s'exécute à la lecture suivante, et les sorties se retrouvent comme elles doivent être le matin.
Un ordinateur démarre avec toutes ses sorties éteintes. Pour remettre les choses d'aplomb après un redémarrage en pleine journée, réglez chaque sortie une fois au début d'après l'heure actuelle, avant la boucle.
Exemple : un chronomètre
Un chronomètre pour un circuit : Entrée (ou une plaque de pression reliée à la face gauche) le lance et l'arrête, r le remet à zéro. Une minuterie redessine l'écran dix fois par seconde, pendant que les touches et la plaque sont traitées tout de suite.
local en_marche = false
local parti_a = 0 -- os.clock() au départ de la course en cours
local acquis = 0 -- secondes comptées par les courses précédentes
local function total()
if en_marche then
return acquis + os.clock() - parti_a
end
return acquis
end
local function dessiner()
local dixiemes = math.floor(total() * 10)
local texte = string.format("%02d:%04.1f", dixiemes // 600, (dixiemes % 600) / 10)
gfx.clear("black")
gfx.text(59, 50, texte, en_marche and "lime" or "white", 7)
gfx.text(88, 130, "ENTRÉE : MARCHE/ARRÊT R : ZÉRO", "gray", 1)
end
local function marche_arret()
if en_marche then
acquis = total()
en_marche = false
else
parti_a = os.clock()
en_marche = true
end
end
dessiner()
local redessin = os.start_timer(0.1)
while true do
local e = os.pull_event()
if e.name == "timer" and e.id == redessin then
dessiner()
redessin = os.start_timer(0.1)
elseif e.name == "key" and e.key == "enter" then
marche_arret()
elseif e.name == "redstone" and rs.get("left") > 0 then
marche_arret()
elseif e.name == "char" and e.char == "r" and not en_marche then
acquis = 0
dessiner()
end
end
Le temps vient d'os.clock, pas du nombre de rafraîchissements : si l'ordinateur ralentit, l'écran se met à jour moins souvent mais le temps reste juste.
Quand la rotation s'arrête
Un ordinateur fonctionne à la rotation. Sa vitesse ne change que le nombre d'instructions qu'il exécute par tick, pas l'écoulement du temps pour lui :
- Une rotation plus lente ralentit le programme, pas les horloges. À 32 tr/min, un Micro-ordinateur exécute un huitième de ses 24 000 instructions par seconde, mais
sleep(1)attend toujours 20 ticks et une minuterie sonne toujours à l'heure. Ce qui s'allonge, c'est le code entre deux attentes. VoirVitesse, mémoire et limites. - Plus de rotation du tout (ou un réseau en surcharge) fige l'ordinateur : le programme s'arrête là où il est,
os.clocketos.times'arrêtent, les pauses et les minuteries cessent de compter, les sorties redstone gardent leur valeur. Les évènements continuent d'entrer dans la file (256 au plus). Quand la rotation revient, tout reprend où c'en était.
Au-delà de 256 tr/min, l'ordinateur n'est pas plus rapide : 256 tr/min, c'est la pleine vitesse.
Quand le chunk se décharge
Quand plus personne n'est à proximité et que son chunk se décharge (ou que le serveur s'arrête), l'ordinateur s'éteint : le programme se termine, la mémoire est perdue, les sorties redstone et les signaux des Liaisons de redstone sont relâchés. Quand le chunk se recharge, l'ordinateur démarre : ses horloges repartent de 0, et il exécute son fichier startup s'il en a un.
Un programme qui doit continuer d'une visite à l'autre doit donc :
- vivre dans le fichier nommé
startup, pour repartir tout seul ; - enregistrer ce dont il doit se souvenir (compteurs, état d'une machine, dernier jour vu) dans un fichier avec
fs.write(), et le relire au début avecfs.read(); - régler ses sorties au début, puisqu'elles sont toutes éteintes après un démarrage.
-- un compteur de lingots pressés qui survit aux redémarrages et aux déchargements
local compte = tonumber(fs.read("compte.txt") or "0")
while true do
os.pull_event("redstone")
if rs.get("left") > 0 then
compte = compte + 1
fs.write("compte.txt", tostring(compte))
end
endLes minuteries ne survivent pas non plus : une minuterie de 10 minutes lancée juste avant le déchargement ne sonne jamais. Pour les longues échéances, écrivez l'échéance dans un fichier, ou servez-vous d'os.day_time, qui appartient au monde et continue d'avancer même quand l'ordinateur est absent.