28 janvier 2004

Optimisation des conditions booléennes avec opérateurs logiques

Pour ceux qui ne le savent pas encore, lorsque vous utiliser des opérateur booléens dans une condtion (&& ou ||), toutes les conditions ne sont pas forcément testées. En effet, une expression booléenne composée uniquement de && est forcément false si un seul de ses éléments est à false. L'évaluation de l'expression d'arrête donc dés qu'un élément false est trouvé. De la même manière, une expression composée de || est true si un seul de ses élements est à true ... :)

L'optimisation de conditions booléennes dans des instructions telles que if(...) ou while(...) passe donc par une estimation des probabilités de chaque expression booléenne unitaire d'être true ou false ... Ainsi, dans une expression &&, nous mettrons en premier les expressions qui ont le plus de chance d'être false, arrêtant ainsi l'évaluation des booléens suivants. 8)

// Retourne true
function getTrue() {
        trace("getTrue");
        return true;
}
// Retourne false
function getFalse() {
        trace("getFalse");
        return false;
}
/* Condition "et" non optimisée
   Les deux fonctions sont appelées */

if (getFalse() && getTrue()) {
        trace("OK");
}
// - Sortie -
// getTrue
// getFalse
/* Condition "et"
   La condition qui a le plus de chances de
   retourner false est mise en premier
   Ainsi, les condtions suivantes ont moins de
   chances d'être executées */

if (getFalse() && getTrue()) {
        trace("OK");
}
// - Sortie -
// getFalse
/* Condition "ou" non optimisée
   Les deux fonctions sont appelées */

if (getTrue() || getFalse()) {
        trace("OK");
}
// - Sortie -
// getFalse
// getTrue
// OK
/* Condition "ou"
   La condition qui a le plus de chances de
   retourner true est mise en premier
   Ainsi, les condtions suivantes ont moins de
   chances d'être executées */

if (getTrue() || getFalse()) {
        trace("OK");
}
// - Sortie -
// getTrue
// OK
Dans le cas donné ici, on sait exactement quel booléen va être retourné, mais de manière générale, il s'agit de faire preuve de bon sens. Il s'agit lors de la conception de ces conditions de faire la part entre la charge induite par l'évaluation d'une expression unitaire, et sa probabilité de mettre fin à l'évaluation de l'expression booléenne entière. Parfois, une condition a plus de chances de mettre fin à l'évaluation, mais provoque beaucoup de calculs, alors qu'une autre partie de l'expression est bien plus rapide à calculer, mais moins de chances d'arrêter l'évaluation. Il faut à ce moment la faire a part des choses entre probabilité et temps d'éxecution. 8| La négation "!" peut parfois servir à intervertir l'ordre de calcul, et ainsi inverser les probabilités. L'ordre sera à redéterminer en fonction de ca. :) if (getTrue() && getFalse()) {
        trace("OK");
}
// est équivalent à
if (!getTrue() || !getFalse()) {
        trace("OK");
}

Ce type d'optimisation est souvent négligé, alors que l'utilisation des conditions est trés fréquente en programmation ... Elle s'applique également à la plupart des langages, bien que certains commencent à évaluer les condtions par la fin ...^^

23 janvier 2004

Saisie des commentaires en Flash

Comme suggéré dans le blog de Stéfane Funaro, créer un formulaire de saisie en Flash pour les commentaires d'un blog permet de se mettre à l'abris de manière quasi-complète des "blogspams" ... J'ai mis en place ici ce type d'outil. :) Pour ceux qui avaient choisis de garder un cookie du blog pour mémoriser leurs noms, email, et site web, vous pouvez l'effacer, étant donné que c'est un ShardeObject qui est maintenant utilisé ! 8)

En ce qui me concerne, ayant un blog "fait maison", je n'ai pas encore eu à faire face a des spammeurs, mais c'est surtout pour des raisons pratiques de formatage du texte par les BBCode que j'ai fait ce formulaire en Flash. Je n'ai pas pris la peine de faire un preload pour les 2,26Ko occupés par l'animation ... 8|

Le problème qui s'est posé à moi est que la classe Selection est assez capricieuse : en effet, elle ne permet de connaitre la zone sélectionnée que pour un élément ayant le focus. Or, lorsque l'on clique sur un bouton, ce bouton prend le focus, et on perd donc les informations concernant le texte qui est selectionné. J'ai donc créé une mini-classe qui permet de garder en permanence la sélection faite sur une zone de texte donnée, et de lui appliquyer des "tags" : dans le cas de ce blog, ces tags sont bien évidemment des BBCodes ... ;)

Je vous la livre telle quelle, vite faite et vite commentée ! :oops:/**
* TextFormater
* Premier brouillon, v0.0 alpha
*
* Surveille la sélection dans un champ texte
* et fournit les outils pour le formater
*/

class TextFormater {
        // TextField à formater
        private var _textfield:TextField;
        // Position de début et de fin de la sélection
        private var _begin:Number;
        private var _end:Number;
        // Constructeur
        // Prend en paramètre le champ texte à formater
        function TextFormater ($tf:TextField) {
                this._textfield = $tf;
                // On crée une propriété dans le TextField
                // qui pointe vers l'objet TextFormater
                this._textfield._formater = this;
                // Quand le champ texte prend le focus
                // on surveille la selection
                this._textfield.onSetFocus = function() {
                        this._formatInterval = setInterval(this._formater,"checkSelection",50);
                }
                // Quand le champ texte perd le focus
                // on arrête de le surveiller
                this._textfield.onKillFocus = function() {
                        clearInterval(this._formatInterval);
                }
        }
        // Surveille la sélection faite dans le champ texte
        function checkSelection() {
                // Si le champ texte a bien le focus
                if (eval(Selection.getFocus()) == this._textfield) {
                        // On renseigne le début et la fin de la selection
                        this._begin = Selection.getBeginIndex();
                        this._end = Selection.getEndIndex();
                }
        }
        // Ajoute un "tag" (BBCode ou autre) au début
        // et à la fin de la sélection
        function addTag($begin:String, $end:String) {
                var txt:String = this._textfield.text;
                // Encadre la sélection avec les tags
                this._textfield.text = txt.slice(0,this._begin) + $begin + txt.slice(this._begin,this._end) + $end + txt.slice(this._end);
                // Donne le focus au champ texte et sélectionne
                // le texte encadré par les tags
                Selection.setFocus(this._textfield);
                Selection.setSelection(this._begin,this._end + $begin.length + $end.length);
        }
}

J'espère que ce formulaire vous rendra la vie plus facile pour égayer vos commentaires, en attendant la version W3C du blog, et les versions Flash du blog et de mon site perso. ^^

9 janvier 2004

Courbe de Bezier : petit effet

Actuellement, j'essaie de mettre en code toutes les petites idées qui me passent par la tête pour réaliser mon site perso. L'une d'elle est un effet elastique sur une courbe de Bezier, que j'ai réalisé vite fait histoire de voir ce que ca donne.





Du coup, je me suis replongé dans les mathématiques de ces courbes, notamment grâce à l'article de Timothée Groleau sur les courbes de Bezier dans Flash dont j'ai déjà parlé, et aussi grâce aux liens qu'il y donne. La difficulté réside dans le fait qu'il faut calculer les cooronnées du point de contrôle (ici juste _y) en fonction de l'endroit où l'on veut que la courbe passe ... rien de bien nouveau, et nombreux sont ceux qui on deja fait ce type d'utilisation des courbes dans Flash, mais voila ma petite contribution ! :)

J'en ai également profité pour me réconcilier un peu avec l'effet elastique des easing de Penner. En effet, bien que fan de ses interpolations, je trouvais l'effet elastique assez peu convaincant ... mais la il passe assez bien ! 8)

Le code en lui-même n'est pas ce qu'il y a de plus rigoureux et générique. En effet, j'ai par exemple profité du fait d'avoir un trait horizontal pour m'éviter un certain nombre de calculs ... :oops: De plus, quelque bugs subsistent encore, notamment lorsque l'on commence à "pousser" la courbe sans aller plus loin et que l'on revient la où on était, elle reste courbée ... ou alors au moment ou la souris rentre au dessus de l'animation. Mais comme je l'ai dit, ce n'est qu'un test assez vite fait ! ;)

::Télécharger xp_curve.zip::

7 janvier 2004

Astuce sur la chaîne de portées et with

Je me suis aperçu d'une utilisation assez singulière de la chaine de portées avec l'instruction with, qui permet de limiter la répétition à outrance de l'endroit ou sont situées des propriétés ou méthodes.

var square = {x:10, y:10, w:100, h:100};<br />
with( square ) {<br />
 with( drawClip = this.createEmptyMovieClip("sq",10) ) {<br />
           _x = x;<br />
           _y = y;<br />
           lineStyle(0,0);<br />
           moveTo(0, 0);<br />
             lineTo(w, 0);<br />
             lineTo(w, h);<br />
             lineTo(0, h);<br />
             lineTo(0,0);<br />
      }<br />
}

Ces deux with imbriqués rajoutent à la chaîne de portées (dans l'ordre de recherche) : le clip crée (sq) et l'objet square. Donc, les instructions vont chercher d'abord dans le clip, et y trouver les méthodes et propriétés _x, _y, lineStyle, moveTo, etc... puis dans l'objet, pour y trouver les propriétés x, y, etc... Et voila, aucune portée n'est répétée à chaque ligne ! 8)

Par contre, cette imbrication est à utiliser avec précaution, étant donné que si le clip possède une propriété ayant le même nom qu'une propriété de l'objet square, c'est celle du clip qui sera utilisée en priorité ... 8|

11 décembre 2003

Parseur ActionScript2 en PHP : premier essai

Pour continuer sur le projet d'un site communautaire autour de classes ActionScript 2, j'ai commencé à développer un parseur AS2 en PHP et ses fantastiques expressions régulières PERL, auxquelles j'ai eu du mal à me mettre, étant habitué aux expressions régulières POSIX. Depuis, les trouvant beaucoup plus puissante, je ne rate pas une occasion de les utiliser ! :D

En effet, lors de la publication de classes sur un site tel que celui que j'imagine, il faut pouvoir connaitre pour une classe la signature de son constructeur et de ses méthodes, afin de s'assurer que d'autres classes les utilisant ne vont pas être perturbées par une modification. Ca peut permettre aussi une génération automatique de commentaires ! 8)

Pour l'instant, il manque encore les import au niveau des dépendances, et la détection des getter/setter ... :°

Le problème est que étant donné que je code un peu toujours de la même manière en terme d'organisation du code, mes tests ne sont pas forcément des plus objectifs ! :=) Donc, si vous avez développé des classes AS2 et que vous voulez les tester dans ce prémice de parseur, ce serait super sympa de me signaler les quelques bugs qui peuvent apparaître sur vos propres classes. Je l'ai essayé sur les classes GPathfinder et GDispatcher de Grant Skinner, et il a l'air de fonctionner ...

Pour les inquiets, je ne sauvegarde absolument rien des codes qui peuvent être saisis dans ce parseur. ;)

1 décembre 2003

Tips : initialiser pluseurs propriétés en même temps

Du temps de mon premier pathfinder, j'avais crée une petit classe Point2, qui contenait des coordonnées. Je voulais également qu'elle soit dynamique, afin d'y mettre les propriétés que je voulait. Le problème était de taper 15 lignes de codes s'il y avait un nombre important de propriétés, et j'ai donc crée cette petite méthode setter pour me faciliter la vie.

dynamic class Dummy {
        function Dummy() {
                // ....
        }
        function set props(o:Object) {
                for (var curProp in o) {
                        this[curProp] = o[curProp];
                }
        }
        function get props():String {
                var prStr:String = "{ ";
                var writeComma:Boolean = false;
                for (var curProp in this) {
                        prStr += writeComma ? "," : "";
                        prStr += curProp+":"+this[curProp]+" ";
                        writeComma = true;
                }
                prStr += "}";
                return prStr;
        }
}
Rien de bien fantastique dans ces quelques lignes, mais c'est son utilisation qui est bien pratique :var myObj = new MyClass();
myObj.props = {name:"LAlex", x:10, y:20};
trace(myObj.name); // LAlex
trace(myObj.x); // 10
trace(myObj.props); // {y:20 ,x:10 ,name:LAlex }

Evidemment, ce n'est pas ce qu'il y a de plus "propre", mais ca peut aider a faire des raccourcis dans le code assez pratiques. :) Ca ne peut également pas être utilisé pour des initialisation multiples comme a=b=c. Pour pouvoir faire cela, il aurait fallu pouvoir retourner une valeur dans le setter, mais ce n'est pas possible comme je l'ai déjà dit dans ce blog ... :( Alors tant qu'à faire, autant que ca serve pour faire un affichage "pratique" ! 8)

25 novembre 2003

[PHP] Nouveau BBCode et protection des adresses e-mail

Aprés avoir reçu un bon paquet de mails "viagra-grospenis-sitedecul" à une adresse que je ne communique jamais, je me suis posé la question des adresses mails qui sont visibles sur certains sites, comme les blogs (y compris le mien) et les sites communautaires. :?

J'ai donc développé un petit script PHP qui va "encoder" les adresses e-mails, puis les envoyer a une fonction javascript qui va s'occuper de les décoder. Ainsi, l'adresse email n'apparaît jamais en clair dans le code source de la page, qui est utilisé par les "aspirateurs d'emails". 8|

L'avantage de ce script est que l'encodage est différent à chaque affichage de la page, ce qui fait qu'il est plus difficile de la récupérer en créant un fonction "en dur". Vous pouvez choisir vous même une liste de valeurs qui vont pouvoir remplacer le symbole '@', et une de ces valeurs sera prise au hasard pour chaque page. 8) En plus, le fait de les décoder pour les afficher avec javascript permet à l'internaute de ne pas être géné, puisqu'il voit exactement la même chose que d'habitude.

Cryptmail fonctionne sur Internet Explorer 6 et sur Firebird (donc sur Mozilla). N'ayant pas de Mac, je ne sais pas s'il fonctionne sur Safari ? 8O Etant du Javascript 1.2, je pense que ca devrait passer. Si certains visiteurs ont un navigateur "exotique" (moins fréquent on va dire :)) ce serait sympa de me dire si les adresses sont visibles correctement (le script tourne deja sur ce blog)! ;)

J'ai intégré ce script à mon parseur BBCode, et j'en ai profité ajouter des fonctionnalités comme la gestion des smileys intégrée au moteur, ce qui me permet également de ne pas afficher de smileys dans les tags de type "norender" (code et autres...). Et tant qu'à faire, j'ai supprimé quelques bugs (affichage des chaines "ressemblant" à une variable PHP comme $var, affichage des antislashs : \ , etc...) 8)

::Télécharger bbcode02.zip (avec cryptmail)::


::Télécharger cryptmail.zip seul::

24 novembre 2003

StringUtils 0.2 : plus optimisée !

J'ai mis en pratique les optimisations qui m'ont été suggérées par Timothee Groleau, et voici donc une version plus optimisée de la classe StringUtils. J'ai également mis en place mon autre implémentation de la méthode reverse, qui se révèle plus performante (MM a bien travaillé sur les tableaux apparemment 8)) ...

Je n'ai finalement pas intégré l'encodage MD5, pour une raison tout bête, qui est que c'est long ! :D En fait, c'est un surcroit de poids (quelques Ko), pour une utilisation moins fréquente que les fonctionnalités actuelles de la classe. Il me paraît mieux de faire une classe dédiée à l'encodage par exemple. Vous pourrez trouver son implémentation par neo-lao dans le commentaire de ce post.

En passant, neo-lao est un de mes partenaires de Flash Forum et il a ouvert un petit site de ressources, ou il mets à disposition ses réalisations Flash et autres : neo-lao ressources. :)

Code de la classe StringUtils/*************************
* StringUtils.as v0.2
* Fonction "utilitaires" de traitement
* des chaînes de caractères
*
*-------------------
* LAlex
* <a href="http://www.lalex.com/">http://www.lalex.com/</a>
*
* ------------------
* Aide à l'optimisation lTrim, rTrim et trim
* Thimothee Groleau
* <a href="http://www.timotheegroleau.com/">http://www.timotheegroleau.com/</a>
*
**************************/

class com.lalex.utils.StringUtils {
        // Caractères considérés comme caractères d'espacement
        private static var DEFAULT_SPACECHARS:String = "

\t"
;
        // StringUtils.replace
        // Cherche une suite de caractères dans une chaine
        // pour la remplacer par une autre
        // ----------
        // $str : Chaine de caractêres à traiter
        // $search : Chaine de caractère à chercher
        // $replace : Chaine de caractère de remplacement
        public static function replace($str:String, $search:String, $replace:String):String {
                return $str.split($search).join($replace);
        }
        private static function spaceStringToObject($space:Object):Object {
                var spObj:Object = new Object();
                // Si aucun caractères n'est spécifié, utilise
                // les caractères par défaut.
                if ($space == undefined) {
                        $space = DEFAULT_SPACECHARS;
                }
                if (typeof $space == "string") {
                        // Remplit l'objet
                        var spLen:Number = $space.length;
                        while(--spLen >= 0) {
                                spObj[$space.charAt(spLen)] = true;
                        }
                } else {
                        spObj = $space;
                }
                return spObj;
        }
        // >> private <<
        //
        // StringUtils._lTrim
        // On supprime tous les caractères d'espacement
        // au début de la chaine de caractères. Un objet
        // dont les propriétés sont les caractères d'espacement
        // lui est transmis.
        private static function _lTrim($str:String,$space:Object):String {
                // Longueur de la chaine restante
                var strLen:Number = $str.length;
                // Position actuelle
                var strPos:Number = 0;
                // Tant que la chaine n'est pas vide
                while (strLen > 0) {
                        // Si on ne trouve pas un espace, on arrete la boucle
                        if (!$space[$str.charAt(strPos)]) {
                                break;
                        }
                        // On avance d'une position
                        strPos++;
                        // On raccourcit "virtuellement" la chaine
                        strLen--;
                }
                // On retourne la chaine sans les caractères trouvés.
                return $str.slice(strPos);
        }
        // >> private <<
        //
        // StringUtils._rTrim
        // On supprime tous les caractères d'espacement
        // à la fin de la chaine de caractères. Un objet
        // dont les propriétés sont les caractères d'espacement
        // lui est transmis
        private static function _rTrim($str:String,$space:Object):String {
                // Longueur de la chaine restante
                var strLen:Number = $str.length;
                // Position actuelle
                var strPos:Number = strLen-1;
                // Tant que la chaine n'est pas vide
                while (strLen > 0) {
                        // Si on ne trouve pas un espace, on arrete la boucle
                        if (!$space[$str.charAt(strPos)]) {
                                break;
                        }
                        // On avance d'une position
                        strPos--;
                        // On raccourcit "virtuellement" la chaine
                        strLen--;
                }
                // On retourne la chaine sans les caractères trouvés.
                return $str.slice(0,strPos+1);
        }
        // StringUtils.lTrim
        // On supprime tous les caractères d'espacement
        // au début de la chaine de caractères. Une chaine
        // dont dont chaque caractère est un caractère d'espacement
        // lui est transmise.
        public static function lTrim($str:String,$space:String):String {
                return _lTrim($str, spaceStringToObject($space));
        }
        // StringUtils.rTrim
        // On supprime tous les caractères d'espacement
        // à la fin de la chaine de caractères. Une chaine
        // dont dont chaque caractère est un caractère d'espacement
        // lui est transmise.
        public static function rTrim($str:String,$space:String):String {
                return _rTrim($str, spaceStringToObject($space));
        }
        // StringUtils.trim
        // On supprime tous les caractères d'espacement
        // au début et à la fin de la chaine de caractères. Une chaine
        // dont dont chaque caractère est un caractère d'espacement
        // lui est transmise.
        public static function trim($str:String, $space:String):String {
                var sp:Object = spaceStringToObject($space);
                return _rTrim(_lTrim($str, sp), sp);
        }
        // StringUtils.stringToArray
        // Convertit une chaine de caractères en tableau
        // contenant un caractère par index.
        public static function stringToArray($str:String):Array {
                return $str.split("");
        }
        // StringUtils.arrayToString
        // Transforme un tableau en chaine de caractère
        // constituée de la concatenation de ses éléments.
        public static function arrayToString($ar:Array):String {
                return "" + $ar.join("");
        }
        // StringUtils.reverse
        // Inverse l'ordre des caractères d'une chaine
        public static function reverse($str:String):String {
                // Transforme la chaine en tableau.
                var ar:Array = $str.split("");
                // Inverse le tableau
                ar.reverse();
                // Transforme le tableau inversé en chaine.
                return ar.join("");
        }
}

::Télécharger StringUtils.zip::

19 novembre 2003

Classe utilitaire pour le traitement des chaînes de caractères

Voici une petit classe que j'ai développé pour y regrouper mes traitements sur les chaînes de caractères. Vous y trouverez les méthodes statiques replace, lTrim, rTrim, trim, stringToArray, arrayToString et reverse ...

Si vous avez des idées de méthodes (ou même des implémentations) à me suggérer, je suis preneur !!! :D

/*************************
* StringUtils.as v0.1
* Fonction "utilitaires" de traitement
* des chaînes de caractères
*-------------------
* LAlex
* <a href="http://www.lalex.com/">http://www.lalex.com/</a>
**************************/

class com.lalex.utils.StringUtils {
        // Caractères considérés comme caractères d'espacement
        private static var SPACE_CHARS:Array = [" ","
"
,"
"
,"\t"];
        // StringUtils.replace
        // Cherche une suite de caractères dans une chaine
        // pour la remplacer par une autre
        // ----------
        // $str : Chaine de caractêres à traiter
        // $search : Chaine de caractère à chercher
        // $replace : Chaine de caractère de remplacement
        public static function replace($str:String, $search:String, $replace:String):String {
                // Debut de la recherche courante
                var stsearch:Number = 0;
                // Position trouvée
                var fnd:Number = -1;
                // Retour
                var ret:String = "";
                // Tant qu'on trouve la chaine recherchée
                while ((fnd = $str.indexOf($search,stsearch)) != -1) {
                        // On rajoute du début de la recherche à la position
                        // trouvée, puis on rajoute la chaine de remplacement
                        ret += $str.slice(stsearch,fnd) + $replace;
                        // On déplace le début de la recherche suivante
                        stsearch = fnd+$search.length;
                }
                // On rajoute la fin de la chaine
                ret += $str.slice(stsearch,$str.length);
                // On retourne le résultat
                return ret;
        }
        // StringUtils.lTrim
        // On supprime tous les caractères d'espacement
        // au début de la chaine de caractères.
        public static function lTrim($str:String):String {
                // Boucle infinie (arretée par les return)
                while (true) {
                        // Si la chaine est vide, on la retourne
                        if ($str == "") {
                                return $str;
                        }
                        // On boucle sur les espaces
                        var numSpace:Number = SPACE_CHARS.length;
                        while (--numSpace >= 0) {
                                // Si un espace est trouvé, on l'enlève de la chaîne
                                // et on arrête la boucle.
                                if ($str.charAt(0) == SPACE_CHARS[numSpace]) {
                                        $str = $str.slice(1);
                                        break;
                                }
                        }
                        // Si numSpace est négatif, la boucle entière a étée
                        // faite, donc aucun espace n'est trouvé.
                        // On retourne donc la chaine.
                        if (numSpace < 0) {
                                return $str;
                        }
                }
        }
        // StringUtils.rTrim
        // On supprime tous les caractères d'espacement
        // à la fin de la chaine de caractères.
        public static function rTrim($str:String):String {
                // On stocke la longueur de la chaine
                // pour éviter d'y accéder plusieurs fois
                var len:Number = $str.length-1;
                // Boucle infinie (arretée par les return)
                while(true) {
                        if ($str == "") {
                                return $str;
                        }
                        // Si la chaine est vide, on la retourne
                        var numSpace:Number = SPACE_CHARS.length;
                        while (--numSpace >= 0) {
                                // Si un espace est trouvé, on l'enlève de la chaîne
                                // et on arrête la boucle. On retire 1 à la longueur
                                // de la chaîne.
                                if ($str.charAt(len) == SPACE_CHARS[numSpace]) {
                                        $str = $str.slice(0,-1);
                                        len--;
                                        break;
                                }
                        }
                        // Si numSpace est négatif, la boucle entière a étée
                        // faite, donc aucun espace n'est trouvé.
                        // On retourne donc la chaine.
                        if (numSpace < 0) {
                                return $str;
                        }
                }
        }
        // StringUtils.trim
        // On supprime tous les caractères d'espacement
        // au début et à la fin de la chaine de caractères.
        public static function trim($str:String):String {
                return rTrim(lTrim($str));
        }
        // StringUtils.stringToArray
        // Convertit une chaine de caractères en tableau
        // contenant un caractère par index.
        public static function stringToArray($str:String):Array {
                return $str.split("");
        }
        // StringUtils.arrayToString
        // Transforme un tableau en chaine de caractère
        // constituée de la concatenation de ses éléments.
        public static function arrayToString($ar:Array):String {
                return "" + $ar.join("");
        }
        // StringUtils.reverse
        // Inverse l'ordre des caractères d'une chaine
        public static function reverse($str:String):String {
                var rev:String = "";
                var i:Number = $str.length;
                while (--i >= 0) {
                        rev += $str.charAt(i);
                }
                return rev;
                /*
                --- Autre implémentation
                --- sans doute moins rapide
                var ar:Array = $str.split("");
                ar.reverse();
                return ar.join(""); */

        }
}

::Télécharger StringUtils.zip::

18 novembre 2003

Hériter de BroadcasterMX : il manque pas quelque chose ?

Toujours sur mon moteur de tilegame en MVC (d'abord en 2D, puis en 3D iso), j'utilise pour la diffusion de mes évenements la classe BroadcasterMX au moyen de l'héritage. Ca permet de ne pas passer par la méthode initialize(), et également d'initialiser le prototype au lieu de l'instance. :)

Or, il se trouve que cette classe ne possede pas de constructeur !!! 8O Ce qui oblige a initialiser le tableau _listeners dans le constructeur de la classe fille. Une petite modification simple consiste donc à rajouter trois ligne dans le code de la classe :function BroadcasterMX() {
   this._listeners = new Array();
}
Il suffit ensuite de faire un simple super() dans la classe fille ...

Oulala, et l'EULA ? Je vais me faire taper sur les doigts moi !!! :D 8)

17 novembre 2003

La classe XML manque de dynamisme

Il semble que la classe XML de Flash ne soit pas déclarée comme étant dynamic. Voila qui pose pas mal de problème pour tout ce qui est callback, souvent utilisé dans l'évenement onLoad ... :? Si on veut accéder à un objet via une propriété de l'objet XML, on se voit obligé de ne pas effectuer de typage strict ... 8|

class Dummy {
        var node:String;
        function Dummy() {
                // ...
        }
        function loadFromXml($file:String) {
                var ldXml:XML = new XML();
                ldXml.callback = this;
                ldXml.load($file);
                ldXml.onLoad = function(suc) {
                        if (suc) {
                                this.callback.node = this.firstChild.nodeName;
                        } else {
                                trace("XML File error");
                        }
                }
        }
}
Il n'existe aucune propriété nommée 'callback'. ldXml.callback = this; Si on enlève le typage fort à la déclaration, ca fonctionne trés bien 8| var ldXml = new XML();

14 novembre 2003

Typage fort et downcast : j'enrage !!!

Je n'arrive pas à trouver un moyen de faire du cast, tout en continuant à utiliser le typage fort de MX 2004 ... :? En fait, une simple utilisation du constructeur permet de ne pas provoquer d'erreur de compilation, mais au lieu de faire un downcast, cela me renvoie null ... :(

Pour ceux qui ne connaissent pas le terme "downcast", ca correspond à changer le type d'un instance vers une classe qui hérite de sa classe actuelle. Cette classe est en fait située "plus bas" dans la chaîne d'héritage, et le cast est la conversion d'un type à un autre, d'où le terme de downcast ... :) Par exemple, une voiture est un véhicule, mais un véhicule n'est pas forcément une voiture. Si on a une instance d'un véhicule et qu'on la caste vers la classe voiture, on va downcaster cette instance ... Un cast dans l'autre sens (de voiture vers véhicule) est un upcast, et il est le plus souvent effectué de manière implicite ...

Revenons à ce que je disais :// classe Parent
class Parent {
        function Parent() {
        }
        function doIt() {
                trace("doIt");
        }
        function toString():String {
                return "Parent instance";
        }
}
// classe Child
class Child extends Parent {
        function Child() {
                super();
        }
        function doItChild(test:Child) {
                trace(">> doItChild");
                trace(test);
        }
        function toString():String {
                return "Child instance";
        }
}
/********** Utilisation **********/
var par : Parent = new Parent();
trace(par); // Parent instance
var chi : Child = new Child();
chi.doItChild(Child(par));
// >> doItChild
// null

Je ne vois pas dans ce cas la ce qui pourrait empêcher le downcast, mais apparemment ca lui pose un problème ... 8| Décidemment, ce typage fort pose plus de problèmes qu'il n'en résout ... :? D'ailleurs, vous devez commencer à me trouver lourd avec ca car je n'ose pas compter combien de posts j'ai fait sur ce sujet !!! :D Mais la je me retrouve encore une fois à buter la dessus, et ca me fais enrager une fois de plus !!! :?

4 novembre 2003

Gestion des Movieclips par Flash

Ted Patrick vient de donner sur la liste Flashcoders une explication de la manière dont flash gère ses données, et plus particulièrement les movieclips sur ce thread. En effet, les quatre types de données gérées par le Flash Player sont

  • Les types simples (nombre, chaines, booléens), accédés par valeur
  • Les objets, accédés par référence
  • Les fonctions, accédées par référence
  • les movieclips, accédés par profondeur.
En fait, c'est la dernière catégorie que je trouve trés interessante. En fait, on peut avoir plusieurs clips qui ont le même nom d'instance, c'est celui qui a la plus petite profondeur qui va avoir la priorité ://add 2 movieclips of the same name
a = _level0.createEmptyMovieClip("ted",1);
b = _level0.createEmptyMovieClip("ted",2);
//which one is named ted? >> Lowest Depth Wins!
trace(_level0.ted.getDepth()) // 1
//trace the references a & b
trace(a.getDepth()) // 1
trace(b.getDepth()) // 2
//add some data
_level0.ted =23
//Data, Objects,Functions, Primitves can overshadow, movieclip path
trace(_level0.ted) // 23

Sur ce, il conseille de travailler les movieclips toujours par référence (valeurs de retour de createEmptyMovieClip par exemple), pour éviter ce type de problèmes.

Voila pourquoi dans Flash MX 2004 il a du être plutôt facile d'implémenter la méthode MovieClip.getInstanceAtDepth .... ;)

28 octobre 2003

Interaction Flash-Javascript avec fscommand et les Flash Methods

J'ai voulu essayer les interactions possibles entre Flash et javascript, que ce soit dans une sens (appel d'une fonction javascript avec Flash), ou dans l'autre (interaction sur l'animation Flash avec Javascript) ... Eh bien je suis assez déçu des performances ... :( Je pense que ce sont les performances du javascript qui sont un peu légères ... 8|

Ce test consiste en fait à regarder en permanence la position d'une popup, et de déplacer un movieclip dans l'animation Flash pour donner l'impression que la popup est un masque que l'on peut déplacer par drag&drop .... Vous pouvez aussi télécharger l'archive qui en contient le code ... Voir le test (testé sur IE, je ne sais pas si ca marche sur les navigateurs de type mozilla ou opera)

::Télécharger js-flash.zip::

20 octobre 2003

Comportement de instanceof avec les types simples

Timothée Groleau propose dans ce post une implémentation en ActionScript de l'instruction native instanceof. Il se trouve que cette implémentation a un comportement que je qualifierai de logique, mais qui n'est pas exactement le même que celui de l'instruction native ... 8|

En effet, instanceof ne renvoie pas true lorsque l'on a pas utilisé de constructeur pour les classes Number, String ou Boolean. Il ne considère donc pas que 10 est une instance de la classe Number. Pour pouvoir l'utiliser avec un nombre, il faut le caster en faisant Number(10), ce qui va automatiquement renvoyer true évidemment ... ;) Cela est sûrement dû à la manière dont Flash gère ces types de variables, mais malgré tout, c'est assez destabilisant ... 8|

L'implémentation de instanceof serait :myInstanceOf = function(obj, theClass) {
        if (!obj || !theClass.prototype) return false;
        var t = obj;
        do {
                if ((t = t.__proto__) == theClass.prototype) return true;
        } while (t != null);
        return false;
}
Voyons la différence de comportement entre les deux :var nb = 10;
trace("instanceof : " + (nb instanceof Number)); // instanceof : false
trace("prototypes : " + (nb.__proto__ == Number.prototype)); // prototypes : true
trace("myInstanceOf : " + myInstanceOf(nb, Number)); // myInstanceOf : true
Le pire est que même le fait de rajouter le typage fort ne modifie pas le comportement de instanceof.var nb:Number = 10;
trace("instanceof : " + (nb instanceof Number)); // instanceof : false
trace("prototypes : " + (nb.__proto__ == Number.prototype)); // prototypes : true
trace("myInstanceOf : " + myInstanceOf(nb, Number)); // myInstanceOf : true

En résumé, je précise au moment de la déclaration de la variable qu'elle est une instance de la classe Number, et instanceof me dit le contraire... 8O

16 octobre 2003

Optimisation de l'accés aux tableaux

Voici une petite astuce que je pratique depuis longtemps en me basant sur ma propre logique, mais je me suis décidé à la benchmarquer (euh ... ca s'écrit comme ca ? :roll:)

Il s'agit d'éviter le plus possible d'accéder à un élément de tableau (pendant une boucle par exemple) ou à sa longueur. Si l'on a besoin d'accéder plusieurs fois au même élément d'un tableau, il est plus performant de le mettre dans une variable, la modifier, puis la remettre dans le tableau (évidemment, si c'est un objet, vous n'avez pas besoin de la remettre dans le tableau ;)). Le principe est le même pour la longueur du tableau.

Voici le "bench" que j'ai fait// On crée un tableau de 100.000 cases
var tst = new Array(100000);
// On va accéder à un élément
// situé peu à prés au milieu (60.000)
tst[60000] = 10;
/** Accés au tableau à chaque boucle **/
var t = getTimer();
for (var i=0 ; i<200000 ; i++) {
        // Accés à la longueur
        tst.length;
        // Accés à l'élément
        tst[60000] += 1;
}
// Affichage du temps
trace("Accés au tableau : " + (getTimer() - t) + " ms.");
// Reinitialisation de la valeur
tst[60000] = 10;
/** Accés à une variable **/
t = getTimer();
// Initialisation de la variable
var elt = tst[60000];
// Initialisationde la longueur
var ln = tst.length;
for (var i=0 ; i<200000 ; i++) {
        // Acces à la valeur de la longueur
        ln;
        // Modification de la valeur
        elt += 1;
}
// On remet la valeur dans le tableau
tst[60000] = elt;
// Affichage du temps
trace("Accés aux variables : " + (getTimer() - t) + " ms.");
Ce qui nous donne en sortieAccés au tableau : 3217 ms. Accés aux variables : 2367 ms.Le gain est en général de l'ordre d'une seconde, ce qui est énorme ! 8O Ca ne parait pas grand chose, mais réflechissez à toutes les boucles for que vous faites sur un tableau, en testant la longueur comme condition d'arrêt ... ;) Il accéde à la propriété length à chaque boucle !!! :roll: Il vaut mieux mettre la longuer dans une variable qui va servir pour la condition du for. Sinon, si l'ordre vous importe peu, vous pouvez utiliser une boucle while qio commence par la longuer du tableau/**** Mauvaise utilisation ****/
for (var i=0 ; i<monTableau.length ; i++) {
   // ...
}
/**** Bonne utilisation ****/
var len:Number = monTableau.length;
for (var i=0 ; i<len ; i++) {
   // ...
}
/*** Autre bonne utilisation ****/
var i:Number = monTableau.length;
while (--i >= 0) {
   // ...
}

Vous pouvez aussi utilise for ( ... in ...), mais je ne sais pas comment ca marche ...:P

Ce n'est certainement pas nouveau pour beaucoup d'entre vous, mais ca peut toujours servir ... 8)

Bug (?) : super, qui es-tu donc ?

MAJ : Aprés une étude plus approfondie du sujet avec Timothée Groleau, il apparaît que ce n'est pas vraiment un bug, mais je dirais plutôt un comportement assez particulier de l'instruction super utilisée avec apply ...

Je suis tombé sur un bug assez bizarre avec super, qui intervient lorsque l'on utilise un héritage sur plusieurs niveaux, et l'instruction apply. Une utilisation particulière provoque une récursivité infinie dans l'appel de méthodes.

Voyons tout d'abord le code :// Classe Parent
class Parent {
        function Parent() {
        }
        function doIt() {
                trace("doIt");
        }
}
// Classe Child
class Child extends Parent {
        function Child() {
                super();
        }
        function doIt() {
                super.doIt.apply(this,arguments);
        }
}
// Classe LittleChild
class LittleChild extends Child {
        function LittleChild() {
        }
        function doIt() {
                super.doIt.apply(this,arguments);
        }
}
// Main
var tst = new LittleChild();
tst.doIt();
//256 levels of recursion were exceeded in one action list.
//This is probably an infinite loop.
Le raisonnement est simple : la dernière classe applique la méthode doIt de sa classe mère (Child) qui elle-même applique la méthode doIt de sa classe Mère (Parent). A mon avis, la recursivité est provoqué par le fait que le super dans la méthode Child.doIt pointe encore vers Child.doIt ... :? Pourtant, si on appelle super.doIt sans utiliser apply (donc en faisant uniquement super.doIt()), la recusrsivité ne se fait pas ... Cela voudrait dire que super est en fait lié à this ?!? 8O J'avoue que je nage complètement, alors que je ne pense pas que mon raisonnement soit mauvais. J'ai effectué des tas de tests, mais j'avoue que ja na comprend pas trés bien de quoi il en retourne. Tout d'abord, il faut savoir que super est un objet, donc que se passe-t-il lorsque qu'on execute super() ?!? C'est bien un appel à une fonction ... sauf que super.apply(...) ne fonctionne pas... :( Je me suis aussi demandé si super n'était pas une propriété de this, mais apparemment non. Voici quelques codes essayé dans la classe LittleChild :class LittleChild extends Child {
  function Child() {
    /*** Test 1 ***/
    trace(typeof super); // object
    trace(typeof super.apply) // Erreur à la compilation
    /*** Test 2 ***/
    trace(this["super"]); // undefined
    /*** Test 3 ***/
    trace(super.doIt == Child.prototype.doIt) // true
    /*** Test 4 ***/
    trace(super.__proto__ == Child.prototype) // false
    trace(super.__proto__ == Parent.prototype) // true
  }
}

La dernière série de tests m'emmène à la conclusion que super est une instance de la classe mère, créée à partir de this.constructor ... qui est buggué dans Flash MX 2004 (voir le post à ce sujet) Ensuite, toutes les methodes du prototype sont copiées dans l'instance ... Le tout ensuite est de savoir quel est le super de cet objet super ?!? :roll:

Bon, je pense pas avoir été trés clair, je suis moi-même complètement perdu ... :P Je vais essayer de réfléchir à ca a tête froide, puis de clarifier tout ca ici ... 8)

PS : Si quelqu'un se sent de l'essayer sur Flash MX, ca clarifierait deja le fait que cet bug soit dû à celui de constructor ... ;)

Bug de constructor (Flash 5) de retour dans MX 2004

Pour ceux qui n'ont pas suivi les commentaires du post sur un inconvénient du typage fort, Timothee Groleau et moi nous sommes aperçu qu'un vieux bug issu de Flash 5, corrigé sur Flash MX, a refait surface dans Flash MX 2004. Il s'agit du bug de constructor lors de l'héritage.

En effet, lors de l'héritage, dans une classe fille, la propriété this.constructor pointe sur le constructeur de la classe parent, que ce soit en AS1 ou en AS2. Le problème dans Flash 5 avait été évoqué par Dave Yang : http://www.quantumwave.com/flash/inheritance.html.

Pour exemple, voici un héritage fait en AS1 au moyen de la méthode préconisée par Macromedia, avec l'instruction new :_global.Parent = function() {};
_global.Child = function() {};
Child.prototype = new Parent();
Child.prototype.doIt = function() {
        trace("isParent ? " + (this.constructor == Parent));
        trace("isChild ? " + (this.constructor == Child));
}
var obj = new Child();
obj.doIt();
Compilé avec Flash MX, la sortie est logiquement :isParent ? false isChild ? true Par contre, compilé avec Flash MX 2004, la sortie est isParent ? true isChild ? false De la même manière, l'AS2 utilise égallement la méthode new de Macromedia lors de sa traduction en AS1, et on a donc le même problème :class Child extends Parent {
  // ....
  function doIt() {
   trace(this.constructor == Parent);
  }
}
var c:Child = new Child();
c.doIt(); // true
Le seul moyen qui avait été trouvé pour rectifier ce problème avec Flash 5 était d'initialiser this.constructor à la main dans le constructeur : class Parent {
  function Parent() {
    this.constructor = Parent;
    // ...
  }
}
class Child extends Parent {
  function Child() {
    super();
    this.constructor = Child;
    // ...
  }
}

C'est quand même incroyable que Flash qui se veut maintenant un outil de développement complètement orienté objet ait encore ce genre de disfonctionnements! :? Il ne reste plus qu'à espérer qu'un patch pour MX 2004 tant attendu par tous les développeurs Flash réctifie ce problème ... :roll:

Au fait, vous avez remarqué ? La colorisation de l'AS2 se fait correctement maintenant ! :D

14 octobre 2003

Bibliothèque sur les matrices

Ca code beaucoup en ce moment, mais pas pour moi ... :( Entre deux batailles avec mes composants, j'ai commencé à créer une bibliothèque sur les matrices, accompagnée d'une refonte de ma classe Array2 (tableau à deux dimensions), qui se nomme maintenant Grid.

La classe Matrix est une classe qui hérite de Grid, mais les paramètres du constructeur et des accesseurs sont différents, car on utilise en géneral les matrices sous la forme (ligne, colonne), alors que les tableaux à deux dimensions sont en général utilisés comme un système de coordonnées, soit (x,y), avec x comme colonne et y comme ligne ... :roll: J'ai aussi implémenté le cas particulier de la matrice carrée dans la classe SquareMatrix

La classe Matrix contient les méthodes permettant de multiplier ou d'aditionner entre elles des matrices. Je compte étoffer ensuite cette bibliothèque avec une classe Vector, qui pourrait contenir un nombre n de dimensions (avec pourquoi pas un cas particulier pour les vecteurs à 2 ou 3 dimensions), car un vecteur aprés tout n'est qu'une matrice avec une seule colonne. :P

Cela me permettra ensuite d'utiliser cette classe avec mon moteur 3D, et d'utiliser une matrice de transformation qui contient translation, homotétie et rotation en même temps ... On vera bien si ca le ralentit ou pas ... :)

::Télécharger Matrix.zip::

10 octobre 2003

Encore un blocage a cause du typage fort de l'AS2

Je profite de mes dernières heures de trial MX 2004 au bureau (eh eh, je l'ai pas installé à la maison 8)) pour pousser un (autre) coup de gueule sur la traduction de l'AS2 en AS1 ... :(

Décidemment, AS2 a beau être plus sympa à utiliser, le fait de poser les limitations du typage fort au moment de la compilation n'arrête pas de m'embêter. Il oblige à réécrire deux fois le même code, ce qui va entièrement à l'encontre des principes de base de la POO ... :?

Je veux créer deux classes dont l'une hérite de l'autre. Les constructeurs de ces deux classes prennent les mêmes paramètres. Je veux alors créer une méthode clone qui va me retourner un clone de l'objet (jusque là, rien de bien original). Pourtant, je vais être obligé de réécrire cette méthode pour la classe fille, car le typage fort m'empêche de créer une méthode dans la classe mère qui fonctionnera pour la classe fille. Dans le pire des cas, je veux bien me passer du typage fort pour la fonction clone (ou juste réécrire le typage de la méthode), mais pas réécrire la méthode entière ! :(

Voila ce que je suis obligé de faire :
// classe Parent
class Parent {
   var _prop1:Number;
   var _prop2:Number;
   function Parent(p1:Number) {
      this._prop1 = p1;
      this._prop2 = p1;
   }
   function clone() : Parent {
      var ret : Parent = new Parent(this._prop1);
      return ret;
   }
}
// classe Child
class Child extends Parent {
   function Child(p1:Number) {
      super(p1);
      this._prop2 = p1 + 10;
   }
   function clone() : Child {
      var ret : Child = new Child(this._prop1);
      return ret;
   }
}
Et voila ce que j'aimerais (je devrais ?) pouvoir faire// classe Parent
class Parent {
   var _prop1:Number;
   var _prop2:Number;
   function Parent(p1:Number) {
      this._prop1 = p1;
      this._prop2 = p1;
   }
   function clone() : Parent {
      var ret : Parent = new this.constructor(this._prop1);
      return ret;
   }
}
// classe Child
class Child extends Parent {
   function Child(p1:Number) {
      super(p1);
      this._prop2 = p1 + 10;
   }
   function clone() : Child {
      return super.clone();
   }
}
La deuxième possibilité serait possible avec un typage fort à l'execution (runtime) et pas à la compilation. J'ai bien essayé d'enlever le typage de la méthode clone (typage du retour et typage de la variable ret), mais il ne m'autorise pas à faire le new this.constructor, prétextant que this.constructor n'est pas une fonction, et en m'affichant comme message : A function call on a non-function was attempted. var ret = new this.constructor(this._prop1); Pourtant, le script suivant ne peut pas mentir :roll:trace(typeof this.constructor); // Affiche : function

- page 3 de 4 -