TapTempo, c'est plus ce que c'était, on a vu a de tout, mais pas encore de PHP, redonnons lui sa gloire.
J'ai écrit plusieurs versions différentes de ce code, mais je vais seulement en donner une, que je trouve intéressante pour plusieurs raisons.
Déjà, le PHP en lui même donne un code plutôt lisible, et sans surprise, ensuite:
- PHP n'a pas de types de liste avancés (ou peu) dans sa bibliothèque standard, j'ai opté ici pour une implémentation très primitive d'un ring buffer pour compter les entrées. Aussi bizarre que ça puisse paraître, je pense pas qu'on puisse faire plus performant (cf. la classe
TapRing
dans le code), - la magie de la fonction
overwriteLine()
que vous pouvez admirer, hideuse mais fonctionnelle (merci au composant console de Symfony qui m'a donné cette solution), - le trick bien hideux qui va avec la ligne
\system("stty -icanon");
trouvé au hasard sur Stack Overflow (une méthode plus native existe, je n'ai juste pas pris le temps dela copier-collerl'implémenter).
Maintenant passons aux points positive, avec quelques évolution récentes du language :
- l'utilisation de
declare(strict_types=1);
qui rend le typage strict (et oui, PHP a ça maintenant, malheureusement, ce n'est pas statique mais reste dynamique, à savoir que c'est évalué à l’exécution et non à la compilation), - il en va de même pour le typage utilisé dans l'ensemble du code,
- bien entendu, le code vit dans son propre namespace, d'où les
\
précédent les appels de fonctions de la bibliothèque standard qui vivent dans le namespace global, - et puis c'est tout, mais c'est pas si mal.
Roulements de tambours, voici le code:
#!/usr/bin/env php
<?php
declare(strict_types=1);
namespace TapTempo;
/**
* Primitive ring buffer implementation
*/
final class TapRing
{
const MAX_SIZE = 30;
const VALIDITY_THRESHOLD = 5; // Seconds
private $buffer = [];
private $index = 0;
private $thresold;
/**
* Default constructor
*
* @param int $validityThreshold
* Lower is the number, more precise will be the BPM calculation,
* nevertheless you don't want to miss any taps, so default value
* should be OK for slow typers.
*/
public function __construct(int $validityThreshold = self::VALIDITY_THRESHOLD)
{
$this->thresold = $validityThreshold;
}
public function tap(): void
{
$this->buffer[$this->index] = \microtime(true);
$this->index = ($this->index + 1) % self::MAX_SIZE;
}
public function getTempo(): int
{
$now = microtime(true);
$first = $last = 0;
$count = 0;
foreach ($this->buffer as $value) {
if (!$value) {
continue; // Unused buffer position
}
if ($value < $now - $this->thresold) {
continue; // This value is too old to be used
}
if (!$first || $value < $first) {
$first = $value;
}
if (!$last || $value > $last) {
$last = $value;
}
++$count;
}
if ($count > 2) {
return (int)\round($count / ($last - $first) * 60, 0);
}
return 0;
}
public function reset(): void
{
$this->buffer = [];
$this->index = 0;
}
}
function overwriteLine(string $message): void
{
print "\x0D"; // Move the cursor to the beginning of the line
print "\x1B[2K"; // Erase the line
print str_repeat("\x1B[1A\x1B[2K", 1); // Erase previous line
print $message;
}
/**
* Main loop
*/
function loop(): void
{
$buffer = new TapRing();
do {
// fread() would to the trick too
$char = \stream_get_contents(STDIN, 1);
if ('q' === $char) {
print "\n";
break;
} else if (10 === \ord($char)) { // 10 is Enter key ASCII code
$buffer->tap();
}
overwriteLine(\str_pad((string)$buffer->getTempo(), 10, " ", STR_PAD_LEFT));
} while (true);
}
print "Appuyez sur la touche entrée en cadence (\"q\" pour quitter)\n";
// Only hack in this file, please read:
// https://stackoverflow.com/a/3684565
// @todo write it using https://stackoverflow.com/a/21628935 instead
\system("stty -icanon");
loop();
Et pour les barbus, l'OPCode généré pour la fonction TapTempo::tap()
:
function name: tap
L33-37 TapTempo\TapRing::tap() /home/pounard/taptempo/vanilla.php - 0x7f084c088000 + 14 ops
L35 #0 FETCH_OBJ_R THIS "index" @1
L35 #1 INIT_FCALL<1> 96 "microtime"
L35 #2 SEND_VAL true 1
L35 #3 DO_ICALL @3
L35 #4 FETCH_OBJ_W THIS "buffer" @0
L35 #5 ASSIGN_DIM @0 @1
L35 #6 OP_DATA @3
L36 #7 FETCH_OBJ_R THIS "index" @5
L36 #8 ADD @5 1 ~6
L36 #9 FETCH_CLASS_CONSTANT "MAX_SIZE" ~7
L36 #10 MOD ~6 ~7 ~8
L36 #11 ASSIGN_OBJ THIS "index"
L36 #12 OP_DATA ~8
L37 #13 RETURN<-1> null
# Ou pas
Posté par hsyl20 (site web personnel) . Évalué à 9.
https://linuxfr.org/users/napin/journaux/portage-de-taptempo-en-php
[^] # Re: Ou pas
Posté par mzf (site web personnel) . Évalué à 6.
Tant mieux si l'auteur n'était pas au courant de l'autre version comme ça c'est une deuxième approche avec le même langage, ça reste intéressant à lire !
D'ailleurs la première version avait des soucis avec l'entrée standard, est-ce que l'utilisation de
$char = \stream_get_contents(STDIN, 1);
permet d'éviter les mêmes écueils ?[^] # Re: Ou pas
Posté par Faya . Évalué à 5.
Il utilise le même hack que j'avais proposé pour corriger le soucis du buffering de STDIN : stty -icanon .
Et oui je trouve intéressant de voir une 2e implémentation, ça m'a permis de découvrir "la magie de la fonction overwriteLine()" et une implémentation différente du ring buffer.
Petit bug : Pas de fuite mémoire dans celle là, par contre si on utilise un autre touche que Entrée, il efface les lignes précédentes du terminal !
[^] # Re: Ou pas
Posté par Christie Poutrelle (site web personnel) . Évalué à 2. Dernière modification le 13 mars 2018 à 09:11.
En théorie c'est facile de faire du PHP sans fuite mémoire, faut éviter les static et les global (EDIT: et fgets apparament).
Oui en effet, j'ai pas pris le temps de donner un peu d'amour à ce code !
[^] # Re: Ou pas
Posté par totof2000 . Évalué à 0.
Moi en pratique j'y arrive : je ne code pas e PHP.
[^] # Re: Ou pas
Posté par Christie Poutrelle (site web personnel) . Évalué à 2. Dernière modification le 13 mars 2018 à 09:12.
Le soucis avec l'entrée standard en PHP, c'est que tant que tu n'as pas de line feed, les fonctions de lecture du flux ne te donnent rien. Je t'avoue que
je n'ai pas réellement pris le temps de chercher pourquoila réponse est dans le commentaire lié plus haut:stty -icanon
désactive le buffering de STDIN, j'image qu'il est bufferisé pour des raisons de performance à l'origine (quand on lit le thread Stack Overflow là: https://stackoverflow.com/questions/3684367/php-cli-how-to-read-a-single-character-of-input-from-the-tty-without-waiting-f/3684565#3684565 on se rend compte que c'est bien dans le cas des remote terminals). Dans l'absolu, fread() et stream_get_contents() ici sont équivalents (j'ai testé les deux).[^] # Re: Ou pas
Posté par kantien . Évalué à 3.
C'est le fonctionnement canonique (d'où le nom de l'option ;-) de l'entrée standard d'un terminal dans les systèmes Unix.
man termios
pour de plus amples informations (en désactivant la fonctionecho
, l'entrée standard ne renvoie pas ce qu'elle reçoit sur la sortie standard, par exemple).Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Ou pas
Posté par kantien . Évalué à 5. Dernière modification le 13 mars 2018 à 10:46.
Cela étant tu devrais peut être faire un appel à
stty icanon
avant de quitter ton programme, pour remettre le terminal dans sa configuration initiale.Pour l'option
echo
, tu peux jouer avec cette commande dans ton terminal :Attention : après on ne voit plus à l'écran ce que l'on tape (pratique pour la saisie d'un mot de passe ;-), il faut taper à l'aveugle la commande :
pour remettre les choses en ordre. :-)
Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Ou pas
Posté par kantien . Évalué à 2.
Après réflexion, vu les contraintes du programme, le mieux serait sans doute de faire :
avant de rentrer dans la boucle, et :
pour rétablir le terminal dans un état sain avant de quitter le programme (cf
man stty
pour les explications).Sapere aude ! Aie le courage de te servir de ton propre entendement. Voilà la devise des Lumières.
[^] # Re: Ou pas
Posté par Christie Poutrelle (site web personnel) . Évalué à 2.
Ah c'est marrant, je suis pourtant remonté dans mon historique je l'avais pas trouvé !
# Shame on you.
Posté par Pipo Le Clown . Évalué à -10.
Mec, tu devrais avoir honte.
[^] # Re: Shame on you.
Posté par Christie Poutrelle (site web personnel) . Évalué à 2.
Je t'ai reconnu, monsieur Delphi.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.