Journal Portage de TapTempo en PHP

Posté par . Licence CC by-sa
Tags :
16
3
mar.
2018

Aie ! Non, pas les doigts ! Ouille !

Mesdames et Messieurs,

Je prends le train en marche et vous fais profiter de cette version de TapTempo en PHP !

La source est disponible dans ce journal et sur Github : taptempo-php.

Si l'on résume, on en est maintenant à : Ada, Bash, Haskell, JavaScript, Perl, Perl6, PHP, Python 2.7, PWA, Rust, Wren.

Écrit sous forme d'une classe statique, j'ai essayé de rester au plus simple tout en structurant le code sans multiplier le nombre de méthodes.

Cette version n'est donc pas un vrai portage mais plutôt une interprétation.

À noter qu'en raison de la gestion de l'entrée standard (via php TapTempo.php du moins), entrer des caractères puis appuyer sur <Enter> donnera un résultat aléatoire. J'ai délibérément ignoré ce cas de figure.

Pour avoir testé le script sur un Intel i5 et un Odroid C2+, j'obtiens des résultats très similaires, mais je serais curieux de savoir s'il y a des machines où le résultat diffère.

Enfin, j'ai également gardé la précision décimale par defaut, sans raison particulière.

Pour ceux qui ont souvent vu des critiques sur PHP et qui ne comprennent pas forcément, vous pourrez voir par vous même qu'au delà du style employé, le langage est un peu bâtard, comme par exemple les fonctions array_push, array_map et; sorti de nulle part, count alors qu'on s'attendrait à array_count.

<?php

stream_set_blocking(STDIN, false);

class TapTempo {

    private static $taps = [];
    private static $interval = 60;
    private static $lastInterval;
    private static $intervals;
    private static $bpm;

    public static function tap() {
        array_push(self::$taps, microtime(true));
        self::getBpm() && self::printResult();
    }

    public static function greet() {
        echo "Welcome to this PHP implementation of TapTempo.\n\n";
        echo "Press <Enter> to start.\n";
        echo "Press <Ctrl-C> to quit.\n";
    }

    private static function printResult(){
        echo "BPM : ".self::$bpm;
        echo "\n";
    }

    private static function getBpm():bool {
        $c = count(self::$taps);

        if ($c === 1) {
            echo "First Tap !";
            return false;
        }elseif ($c > 5) {
            array_shift(self::$taps);
        }

        self::lastInterval();

        array_map(function(float $e){
            array_push(self::$intervals, $e - self::$lastInterval);
        }, self::$taps);

        $mul = self::$interval / array_sum(self::$intervals);
        self::$bpm = abs(count(self::$intervals) * $mul * 2); 

        return true;
    }

    private static function lastInterval() {
        end(self::$taps);
        self::$lastInterval = current(self::$taps);
        reset(self::$taps);
        self::$intervals = [];
    }

}


TapTempo::greet();

while (true) {
    if (fgetc(STDIN) !== false) TapTempo::tap();
}

À la vôtre !

  • # Quelques fautes...

    Posté par . Évalué à 2.

    Ah, j'ai laissé traîner quelques fautes dans le texte.

    J'aurais bien édité pour corriger, mais je n'ai pas vu le lien pour le faire.
    Désolé si ça fait mal aux yeux !

  • # Entré standard

    Posté par (page perso) . Évalué à 2.

    Bravo pour avoir relevé le défi :-)

    À noter qu'en raison de la gestion de l'entrée standard (via php TapTempo.php du moins), entrer des caractères puis appuyer sur donnera un résultat aléatoire. J'ai délibérément ignoré ce cas de figure.
    Pour avoir testé le script sur un Intel i5 et un Odroid C2+, j'obtiens des résultats très similaires, mais je serais curieux de savoir s'il y a des machines où le résultat diffère.

    Je ne suis pas très familier avec le php : en quoi consiste le soucis avec l'entrée standard ? Le temps de réponse a une latence importante ?

    • [^] # Re: Entré standard

      Posté par . Évalué à 4.

      Il utilise fgetc qui ne lit qu'un caractère à la fois. Si on saisit plusieurs caractères avant d'appuyer sur Entrée, TapTemp::tap() sera appelé pour chaque caractère lu. Remplacer fgetc par fgets règle ce soucis.

  • # Ça fuit !

    Posté par . Évalué à 10.

    Ton script a fait planter ma machine en remplissant lentement mais sûrement la RAM (et ma swap était déjà atteinte…). Donc j'ai cherché comment corriger et apparemment fgets dans une boucle infinie c'est une mauvaise idée : https://www.google.com/search?q=php+fgets+memory+leak

    J'ai d'abord testé gc_enable et gc_collect_cycles disponibles depuis PHP 5.3 mais ma RAM continuait à se remplir à chaque exécution. J'ai finalement trouvé stream_select : "The streams listed in the read array will be watched to see if characters become available for reading"

    Donc j'ai pu boucher la fuite avec

    // Disable buffering
    system("stty -icanon -echo min 0 time 0");
    
    // Stream arrays
    $in = [STDIN];
    $out=NULL;
    $oob = NULL;
    
    while (stream_select($in , $out , $oob, NULL )) {
        if( fgetc(STDIN) !== false ) {
            TapTempo::tap();
        }
    }

    L'appel system("stty -icanon") permet de désactiver le buffer de STDIN d'après https://www.mail-archive.com/php-general@lists.php.net/msg151195.html

    • [^] # Re: Ça fuit !

      Posté par . Évalué à 2. Dernière modification le 04/03/18 à 06:09.

      J'ai testé + corrigé le code sur Github, et l'effet secondaire sympa est que l'on peut maintenant utiliser les autres touches comme la barre d'espace pour effectuer un tap.

      Pour quelqu'un comme moi qui manipule très rarement STDIN, ton commentaire est vraiment appréciable, merci beaucoup.

      • [^] # Re: Ça fuit !

        Posté par . Évalué à 6.

        Je t'avoue que j'ai rarement codé en php-cli et encore moins avec STDIN donc c'est au prix de longues recherches et de nombreux tests que j'ai trouvé la solution…

        Sinon tu peux aussi rajouter un shebang au début de ton script pour éviter de faire php TapTempo.php. PHP sera appelé automatiquement :

        #!/usr/bin/php
  • # Static ! Ouate !

    Posté par (page perso) . Évalué à 1. Dernière modification le 13/03/18 à 09:17.

    Je vois que tu as fais le choix d'écrire le code sous la forme d'une classique avec que des méthodes statiques: dans l'absolu, le code est plus rapide écrit de cette façon, mais d'un autre côté, ça rend toutes les variables de ton code globales par définition, et donc non testable et sujet à effet de bord.

    La mode dans le PHP actuel (sauf avec Laravel, qui à mon sens n'est pas un framework mais une blague, attention, troll detected) c'est plutôt d'essayer d'aller vers l'immutabilité et de se rapprocher du fonctionnel (pour avoir le moins d'effets de bord possible et améliorer la testabilité du code).

    Ça reste bien entendu très théorique et religieux tout ça.

    • [^] # Re: Static ! Ouate !

      Posté par . Évalué à 1.

      Bonjour,

      J'ai du mal a saisir la ou tu veux en venir, et meme ce que tu entends par "non testable" ou encore par "variables globales".
      Bien que je ne sois pas tres fan de religion (j'ai rien contre la theorie), la mode est egalement un truc qui me depasse : je prefere ecrire du code qui fonctionne et qui est robuste plutot que du code qui est "a la mode".

      Si tu pouvais etayer tes propos avec des exemples, ca serait sympa de ta part :-)

      Note : cette classe est discutable sur, au moins, les responsabilites qui lui sont donnees concernant l'affichage, mais je pense qu'il faut savoir rester simple pour partager du code avec le reste du monde : a loisir de celui qui souhaite le reutiliser de l'implementer a sa maniere.

      Note 2 : je n'ai pas d'accents sur la machine depuis laquelle je tape ce commentaire, sorry !

      • [^] # Re: Static ! Ouate !

        Posté par (page perso) . Évalué à 2.

        Je me demande surtout pourquoi tu as mis toutes tes méthodes et propriétés et "static", ça n'a pas de sens: dans ce cas là écrit juste des fonctions, sachant que l'utilisation de namespaces fournit le seul avantage qu'une classe entièrement statique peut t'apporter (c'est à dire grouper dans un même espace de nom tes propriétés et méthodes).

        Après, dans la pratique, dans ce code en particulier c'est pas grave, mais le fait que tout soit statique de manière générale induit un global state, qu'on essaye de toujours éviter car dans un projet plus complexe, ça augmente la surface de potentiels effets de bord, et va plutôt dans le sens inverse de l'immutabilité.

        • [^] # Re: Static ! Ouate !

          Posté par . Évalué à -2.

          Pas de details, pas de reponse concernant les tests et les "variables globales" … Tu m'excuseras, mais je ne trouve que peu d'interet dans les conversations a sens unique.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.