22 août 2006

Proxy et Interval

Pas grand chose de neuf en ce moment, je passe beaucoup de temps en maintenance de code. Du coup, je vous propose deux petites classes qui me servent énormément au quotidien. Chaque développeur qui passe ses journées dessus doit avoir un équivalent je suppose: une classe de proxy, héritière du fameux Delegate, et une classe Interval, qui m'évite la permanente prise de tête avec les ids des setInterval...

Lire la suite...

9 juin 2006

Number = parseInt

Number, en plus d'être une classe, est également une fonction, tout comme le sont Array, Boolean et String. Elle permet de convertir un objet quelconque en une instance de Number.

Seulement, ce qui n'est pas précisé dans la doc, c'est que cette fonction suit les mêmes règles que parseInt, sans pouvoir toutefois lui passer de second paramètre, qui précise dans quelle base doit se faire la conversion. Ce qui signifie que :

  • Les chaines de caractères commencant par "0x" seront considérées comme héxadécimales
  • Les chaines de caractères commencant par "0" seront considérées comme octales.

Et c'est sur ce dernier point que je me suis fait avoir, en cherchant un long moment pourquoi mon "062" devenait 50... :\ J'ai donc du remplacer toutes mes conversions :var num:Number = Number("062") // num = 50
// par
var num:Number = parseInt("062", 10) // num = 62

Donc voilà, si ca peut profiter à quelqu'un.. ;)

10 mai 2006

Constructeur et arguments

Lors qu'une classe hérite d'une autre, tout l'interêt est bien évidemment de ne pas réimplémenter les methodes de la classe mère. En effet, en l'absence d'une méthode portant le nom demandé, la méthode de la classe mère est appelée, transmettant ainsi tous les arguments. Eh bien ce qui est dommage, c'est que ce n'est pas le cas du constructeur... :\

En effet, si une classe mère prend un argument en paramètre, et qu'une classe fille hérite de cette dernière sans implémenter de constructeur, les paramètres passés au constructeur de la classe fille ne seront pas transmis.

La preuve par l'exemple: j'utilise actuellement un ensemble de managers gérant chacun des objets (Contacts, Articles, etc...) dont les classes implémentent toutes la même interface. Le but est donc de créer un manager "de base" (non instanciable) qui factorise les comportements communs... Chaque manager est lié à un contexte dans l'application, qui est passé en paramètre au constructeur du manager:
/* == Classes utilisées
interface Item
class Contact implements Item
class Context
*/

// Classe mère
class BaseManager {
   private var _context:Context;
   private var _selectedItem:Item;
   // Le constructeur prend un paramètre
   private function BaseManager(ctx : Context) {
      _context = ctx;
   }
   public function getContext() : Context {
      return _context;
   }
   public function getSelectedItem() : Item {
      return _selectedItem;
   }
   private /*protected*/ function setSelectedItem(i : Item) {
      if (i != _selectedItem) {
         _selectedItem = i;
         dispatchEvent({type:"selectionChanged", selected:_selectedItem});
      }
   }
}
class ContactManager
   extends BaseManager {
   // Force le typage à 'Contact'
   private function setSelectedItem(c : Contact) {
      super.setSelectedItem(c);
   }
   public function getSelectedContact() : Contact {
      return Contact(getSelectedItem());
   }
}
// Création du contexte
var ctx:Context = new Context("mainApp");
// Création du ContactManager
var contactManager:ContactManager = new ContactManager(ctx);
// Affichage du contexte
trace(contactManager.getContext()); // undefined
Ceci est trés certainement du à la conversion en AS1. En effet, en AS1, déclarer une classe revient à déclarer son constructeur, et donc a relayer "manuellement" les paramètres du constructeur si besoin. Le "compilo" AS2 du coup crée automatiquement un constructeur dont le code est:function ContactManager() {
   super();
}
alors que dans mon cas, il faut que je crée le constructeur moi-même:function ContactManager(c : Context) {
   super(c);
}

C'est peut-être déjà connu de beaucoup, mais moi j'ai cherché un p'tit moment quand-même... Bref, cela revient encore et toujours au problème d'appeler le constructeur d'une classe mère avec un nombre indéfini d'arguments (l'idéal étant avec apply)... :\

15 février 2006

Switch/case original : tester la classe d'une variable

Pour ceux qui ne lisent pas la mailing-list de OSFlash, Ralf Bokelberg y donne une petite astuce pour tester la classe d'une variable, et de manière plus globale comment éviter une succession de if...else if...else.

En fait, le switch...case a pour limitation de ne servir qu'à comparer une valeur à une autre (ou plusieurs autres), alors qu'un if va vérifier si une condition est vraie. Mais, et c'est là qu'est l'astuce, vérifier si une condition est vraie consiste uniquement à comparer son résultat à true... et voila! 8) Petit exemple:public static function getEnvironement(eng : Engine) : String {
        var env:String = "Twilight zone";
        switch(true) {
                case (eng instanceof Boat):
                        env = "Water";
                        break;
                case (eng instanceof Car):
                case (eng instanceof Motorcycle):
                case (eng instanceof Bus):
                        env = "Road";
                        break;
                case (eng instanceof Plane):
                        env = "Sky";
                        break;
                case (eng instanceof Spacecraft):
                        env = "Outerspace";
                        break;
                default:
                        trace("ET???");
        }
        return env;
}

C'est peut-être connu, et certainement utilisable dans de nombreux langages, mais moi j'ai découvert aujourd'hui, et je trouve l'astuce bien élégante! ^^

5 janvier 2006

Connecteur à la console SOS

Le passage à FDT n'est pas toujours sans douleurs... Les habitudes évoluent, et la façon de coder varie inmanquablement. Un des problèmes auxquels je me suis trouvé confronté a été l'utilisation de la commande 'trace', que l'on peut bien évidemment remplacer par un appel de méthode via l'option -trace de MTASC... Comme j'en ai déjà parlé, j'utilise la console SOS, pour le côté pratique de la connexion socket que sont la connexion à une console sur une machine distante, ou la gestion d'une couleur de fond différente par application (TRES utile !!!)

Le moyen que j'utilise aujourd'hui afin d'éviter les problèmes de non-importation de la classe s'occupant de se connecter à la console consiste à créer un petit répertoire utilitaire contenant les classes utiles à FDT, et de l'inclure dans mes "Linked librairies", et d'en appeler une méthode start(). Pour l'instant, seule le connecteur à la console est présent, mais il se peut que d'autres suivent (notamment mon petit framework de profiling, a venir plus tard :P)

Bref, voici ma classe SOSTracer, utilisée par MTASC avec l'option -trace SOSTracer.out :
/**
 * @author LAlex
 * @version 0.1
 * @since 2006-01-05
 */

class SOSTracer {
        // Console infos
        public static var SOS_HOST:String = "localhost";
        public static var SOS_PORT:Number = 4444;
        // Used for static method 'out'
        private static var __instance:SOSTracer;
        // Used for line number display
        private static var __lineNumber:Number = 1;
        // Allow to display line numbers
        public static var enableLineNumber:Boolean = false;
        // Static function, used for MTASC -trace option
        // SOSTracer.start() must be called before
        public static function out(o) {
                __instance.trace(o);
        }
        // Launch the static SOSTracer with
        // given background color
        public static function start(col:Number):Boolean {
                __instance = new SOSTracer(col);
                return true;
        }
        private var _bufferize:Boolean = true;
        private var _socket: XMLSocket;
        private var _isConnected:Boolean = false;
        private var _color:Number;
        // For later ;oP
//      private var _itv:Number = -1;
        // Buffer
        private var _stack: Array;
        function SOSTracer(col:Number) {
                if (col != undefined) {
                        _color = col;
                }
                _stack = [];
                _socket = new XMLSocket();
                _socket.onConnect = function (suc) {
                        var tr = arguments.callee.tracer;
                        tr._connected(suc);
                };
                _socket.onConnect.tracer = this;
                _socket.connect(SOS_HOST, SOS_PORT);
        }
        // Launched on socket connection
        private function _connected(sc:Boolean) {
                if (sc) {
                        if (_color != undefined) {
                                _socket.send("!SOS<appColor>" + _color + "</appColor>");
                        }
                        _flush();
                        _isConnected = true;
                } else {
                        _bufferize = false;
                        throw new Error("SOS Socket connection failed");
                }
        }
        // Flush buffer
        private function _flush() {
                while(_stack.length) _socket.send(_stack.shift());
        }
        // Display a string (or object representation)
        public function trace(o) {
                var no:String = (enableLineNumber ? "[" + __lineNumber++ + "] " : "") + o.toString();
                if (_isConnected) {
                        _socket.send(no);
                } else if (_bufferize) {
                        _stack.push(no);
                }
        }
}
Le but est ici d'être indépendant de tout autre package, d'où l'utilisation d'une classe XMLSocket "de base" (j'en ai implémenté une plus évoluée), et l'utilisation d'un Delegate "à la main" :P Afin d'éviter de surcharger la connexion, je prévois ensuite d'utiliser un système de bufferisation éventuel, au moyen du buffer utilisé avant la connexion, et d'un setInterval (vous avez surement remarqué que la propriété pour l'intervalle est déjà prévue, mais pas utilisée encore :$). Pour l'utiliser, rien de plus simple, je lance un start() dans mon main et j'utilise trace() dans mon code : public static function main() {
        SOSTracer.start(0XFFFFCC); // Couleur de fond dans la console
}

Pour le déploiement, il suffit de changer dans MTASC l'option -trace SOSTracer.out par -trace no, et de commenter la ligne SOSTracer.start() (pas de import à commenter, du fait de l'absence de package 8))

::Télécharger SOSTracer.zip::

22 décembre 2005

Simuler une énumération et/ou des contantes

Il peut être trés pratique, voire parfois indispensable pour obtenir un code "clean", de ne pouvoir passer une valeur à une méthode, qui doit obligatoirement être une des valeurs "possibles"... C'est ce que l'on appelle des énumérations. Java gère cela nativement grâce à la classe Enum.

De même, avoir la possibilité de créer des constantes est également bien utile. Dans la pratique, il s'agit en fait de créer une propriété statique en lecture seule. Le moyen le plus sûr de faire cela serait de créer une variable statique privée, et un getter statique public.

Voici ce que cela pourrait donner dans le cadre d'une classe d'encryptage:
class Crypter {
        // Private static members
        private static var _NONE:String = "none";
        private static var _MD5:String = "md5";
        private static var _SHA1:String = "sha1";
        // Public constants
        public static function get NONE():String {
                return _NONE;
        }
        public static function get MD5():String {
                return _MD5;
        }
        public static function get SHA1():String {
                return _SHA1;
        }
        private var _cryptMode:String;
        function Crypter(mode:String) {
                _cryptMode = mode;
        }
        function encrypt(s:String):String {
                // Case use private vars, to avoid decreasing
                // performances by using a getter
                switch(_cryptMode) {
                        case _NONE:
                                return s;
                        case _MD5:
                                var md5crypted:String = "";
                                // Perform MD5 cryptage
                                return md5crypted;
                        case _SHA1:
                                var sha1crypted:String = "";
                                // Perform SHA1 cryptage
                                return sha1crypted;
                }
        }
}
// Usage
var myCrypter:Crypter = new Crypter(Crypter.MD5);
trace(myCrypter.encrypt("String to be encrypted"));
Bien que tout cela soit quand-même assez clean, cette implémentation pose quelques problèmes:

  • Tout accés aux constantes (de l'exterieur) fait appel à un getter, et donc les performances peuvent s'en ressentir, selon la fréquence de ces accés
  • Il est possible de passer au constructeur une chaine de caractères qui ne fasse pas partie des valeurs attendues en entrée... Cela peut se résoudre avec un default: dans le case, mais ce n'est pas vraiment une solution satisfaisante...
Dans les premières betas de la librairie de petepx, pixLib, j'avais noté l'utilisation trés élégante qu'avait fait Francis de l'héritage de String pour le typage de ses types d'évenements (classe EventType si je ne m'abuse). A priori, il s'agissait surtout, dans ce cas, de polymorphisme avec EventDispatcher de Macromedia, mais j'ai alors étendu le concept pour pouvoir créer mes énumérations, dans une classe externe.// Enum
class CryptMode
        extends String {
        public static var NONE:CryptMode = new CryptMode("none");
        public static var MD5:CryptMode = new CryptMode("md5");
        public static var SHA1:CryptMode = new CryptMode("sha1");
        private function CryptMode(content:String) {
                super(content);
        }
}
class Crypter {
        private var _cryptMode:CryptMode;
        function Crypter(mode:CryptMode) {
                _cryptMode = mode;
        }
        function encrypt(s:String):String {
                // Case use private vars, to avoid decreasing
                // performances by using a getter
                switch(_cryptMode) {
                        case CryptMode.NONE:
                                return s;
                        case CryptMode.MD5:
                                var md5crypted:String = "";
                                // Perform MD5
                                return md5crypted;
                        case CryptMode.SHA1:
                                var sha1crypted:String = "";
                                // Perform SHA1
                                return sha1crypted;
                }
        }
}
// Usage
var myCrypter:Crypter = new Crypter(CryptMode.MD5);
trace(myCrypter.encrypt("String to be encrypted"));
Cette méthode à tous les avantages:
  • Impossible de rajouter des constantes non-prévues au runtime, du fait du constructeur privé.
  • Toujours grâce au constructeur privé, impossible de modifier la valeur d'une constante, bien qu'il s'agisse d'une propriété publique.
  • Impossible de passer une valeur non prévue à une méthode, si l'argument est typé avec l'énumération.
  • Une compatibilité avec les types de bases. Bien que l'exemple se base sur un héritage de String, il est bien sûr possible de l'utiliser sur n'importe quel type "de base" (Number par exemple) comme sur des types plus complexes. Le '==' continue de fonctionner par exemple :trace(CryptMode.MD5 == "md5"); // true
  • Petit bémol, le switch...case semble utiliser une comparaison stricte (===), donc ne pas oublier de faire un toString() ou valueOf() dans ce cas... ;)
  • Remplace presque parfaitement le fait de ne pas pouvoir créer de constantes dans les interfaces, offrant ainsi la possibilité de disposer d'une série de valeurs dans la création d'une API "pure" (sans aucune implémentation).

Bref, je suis assez content d'avoir trouvé ce moyen de rendre mes codes un peu plus clean. J'utilise dorénavant énormément cette astuce, notamment concernant la diffusion d'évenements, en créant une classe collection contenant les évenements diffusables par une classe définie, puis en utilisant le typage dans la signature de mes addEventListener (en parlant de ça, bientôt un tool de diffusion d'évenements maison).

Wala, wala! ^^

9 décembre 2005

Bug du compilateur Flash 8 (...et moins?)

Le compilateur de Flash 8 semble contenir un bug qui, s'il est mineur, peut provoquer quelques arrachages de cheveux. En fait, lorsque l'on hérite d'une classe en précisant son chemin complet (donc sans import), le compilo va la considérer comme importée, mais ce n'est pas le cas à l'éxecution. La où l'on s'attend donc à obtenir une erreur de compilation, tout se passe bien, et on comprend donc mal où est l'erreur... :\

Petit exemple :class com.lalex.myappli.CustomSocket extends com.lalex.net.ExtendedSocket {
   function CustomSocket() {
      /**
      * ExtendedSocket.CONNECT_FAILED sera 'undefined'
      * car on a pas importé com.lalex.net.ExtendedSocket
      * Pourtant, le compilo laisse passer
      */

      addEventListener(ExtendedSocket.CONNECT_FAILED, this, handleConnectionFail);
   }
   private function handleConnectionFail(o) {
      trace("Connection failed");
   }
}

Bon, je vous l'accorde, on pourrait dire "Ben il n'avait qu'à importer la classe", ou "Il n'avait qu'à donner le chemin complet de la classe!"... mais le compilateur n'est-il pas là pour nous signaler ce genre d'oublis? :o

15 novembre 2005

Typage :Object, le retour

Eh oui, ce détail anodin du compilateur qui nous empêchait de typer avec Object est enfin résolu dans Flash 8. Je trouve ca bien, ca permet de spécifier explicitement que n'importe quel objet peut être transmis en paramètre ou affecté à une variable, et cela fait aussi bien la différence aussi entre une méthode qui ne retourne rien (Void) et un objet quelconque (Object)... 8)

PS: Je suis en plein dans les entrailles de la communication temps réel actuellement: Flash Media Server 2 et connexions XMLSocket... Je devrais avoir de la matière sur ce blog prochainement! ;)

26 septembre 2005

FileLink : Téléchargement de fichier et menu contextuel

En tant qu'utilisateur, je trouve parfois assez exaspérant de devoir ouvrir, par exemple, un fichier PDF dans mon navigateur. Je préfère largement le télécharger et l'ouvrir avec Acrobat Reader...

Lorsqu'il s'agit d'un lien dans une page HTML, il est facile d'utiliser un clic droit (ou CTRL pour les maceux) pour pouvoir choisir entre l'ouvrir réellement ou le télécharger. Par contre, dans un site Flash c'est plus délicat...

C'es pourquoi j'ai créé vite-fait une petite classe qui permet d'assigner à un clip un menu contextuel qui va permettre soit d'ouvrir, soit de télécharger un fichier déterminé. Elle est basique pour l'instant, mais l'on peut prévoir de la faire évoluer avec quelques options (localisation, cible du getURL, comportement par défaut du onRelease, etc...). :) A noter que le téléchargement ne fonctionne que dans un environnement HTTP (cela est du aux restrictions de la classe FileReference), donc on oublie le test dans l'IDE Flash ou dans une page HTML accédée directement depuis le disque dur... :(import flash.net.FileReference;
import mx.utils.Delegate;
/**
 * FileLink
 *
 * @notice Can only be used in HTTP environment due to FileReference restrictions
 * @author  <a href="http://www.lalex.com/">LAlex</a>
 * @version 1.0
 * @since   
 */

class com.lalex.utils.FileLink {
        // File path
        private var _path:String;
        function FileLink(f:String) {
                _path = f;
        }
        /**
         * Assign a link to a movieclip, setting its context menu
         *
         * @usage   fLink.assignTo(myLinkButton, true, true);
         * @param   clip       MovieClip to click to obtain download menu
         * @param   setRelease Set the onRelease handler of the clip ?
         * @param   hide       Hide context menu builtin items ?
         */

        function assignTo(clip:MovieClip, setRelease:Boolean, hide:Boolean) {
                hide = !!hide;
                setRelease = !!setRelease;
                var dl:Function = Delegate.create(this, this.download);
                var op:Function = Delegate.create(this, this.open);
                var m:ContextMenu = new ContextMenu();
                m.customItems.push(new ContextMenuItem("Ouvrir", op));
                m.customItems.push(new ContextMenuItem("Télécharger", dl));
                if (hide)
                        m.hideBuiltInItems();
                if (setRelease)
                        clip.onRelease = op;
                clip.menu = m;
        }
        /**
         * Open a file in the browser
         * Used by a proxy (Delegate) function
         */

        private function open() {
                getURL(_path);
        }
        /**
         * Dowload the file to the local computer
         * Used by a proxy (Delegate) function
         */

        private function download() {
                var f:FileReference = new FileReference();
                f.download(_path);
        }
}
Et voici son utilisation (celle du bouton ci-dessous)import com.lalex.utils.FileLink;
var fl:FileLink = new FileLink("files/FileLink.zip");
fl.assignTo(bt, true, true);




::Télécharger FileLink.zip::

20 septembre 2005

SuperColor devient XColor pour Flash 8

Pour le retour de mon blog, quelque peu perturbé par un changement de serveur (mais qui pulse bien, ca valait le coup :P), je vous propose une version Flash 8 de ma classe SuperColor, qui devient pour l'occasion XColor (pour eXtended Color).

En effet, comme vous l'avez peut-être déjà vu, la classe Color est déclarée deprecated dans Flash 8. Et comme j'aime pas spécialement trop changer mes habitudes de codage (surtout quand elles sont efficaces), j'ai donc refactorisé la classe pour qu'elle puisse être utilisée strictement de la même manière dans Flash 8, sans utiliser une classe qui est à priori vouée à disparaitre... Il s'agissait donc de recréer le comportement de Color en utilisant sa remplacante : ColorTransform!

Etant donné que ma classe héritait de Color, il m'a alors suffit de réimplémenter les méthodes setTransform, getTransform, setRGB et getRGB pour que tout remarche exactement comme avant. C'est reparti pour du Tween facile sur les couleurs! 8)
Par contre, cette classe ne marche que sur Flash 8, pour les "MX2004iens", il suffit de continuer à utiliser SuperColor... ;)

::Télécharger XColor.zip::

22 août 2005

Debug 0.2

Ca ne fait pas longtemps que je re-code en AS2 au quotidien. Et depuis que j'ai repris, j'utilise ma classe Debug intensivement, et la fait donc évoluer en fonction de mes besoins, et aussi des bugs qu'elle peut comporter... :P

Au programme de cette nouvelle version, tout d'abord le fait d'éviter une boucle infinie dans le cas ou un object est composé d'un autre, qui lui-même pointe vers son parent. En gros, c'est comme si on parcourait récursivement un clip, et sa propriété _parent...

Ensuite, une méthode éxécutée au premier appel de dump(), qui va parcourir toutes les classes présentes et leur créer une propriété contenant le nom de la classe, et une propriété conternant le nom du package. Cela permet d'afficher tout ca lors du dump d'un objet... En fait, c'est le seul moyen de retrouver le nom d'une classe sans le préciser dans le code de cette classe... Je crois me souvenir que Liguorien avait déjà évoqué ce sujet, s'inspirant de la partie reflection de la librairie as2lib...

Et troisièmement, la possibilité de spécifier un caractère qui permettra d'ignorer toute propriété dont le nom commence par celui-ci... Par exemple, il m'arrive d'utiliser une propriété contenant un XML dans mes classes. Il me suffit de faire commencer son nom par '$' (caractère par défaut) pour qu'elle ne soit pas incluses dans l'affichage...

/**
* Debug
* @version 0.2
* @package com.lalex.utils
*
* @author LAlex
* @since 2005-08-16
*/

class com.lalex.utils.Debug {
   /**
   * Property used to store class names
   */

   public static var classNameProperty:String = "__className";
   public static var packNameProperty:String = "__packName";
   private static var ignoreChar:String = "$";
   private static var __classInit:Boolean = false;
   /**
   * Trace an object detailled content
   * @param o Object to display
   */

   public static function dump(o) {
                if (!__classInit) {
                        initClassNames();
                        __classInit = true;
                }
                trace(dumpToString(o));
   }
        /**
        * Browse all classes and create a property for class name
        * and a property for package name
        * Browse is recursive
        * @param o Object containing classes
        * @param pa Package name
        */

   public static function initClassNames(o, pa:String) {
                if (!__classInit) {
                        o = o ? o : _global;
                        pa = pa.length ? pa : "";
                        for (var p in o) {
                                if (typeof o[p] == "function") {
                                        o[p][classNameProperty] = p;
                                        o[p][packNameProperty] = pa.length ? pa.substr(0, pa.length-1) : "";
                                        _global["ASSetPropFlags"](o[p], classNameProperty + "," + packNameProperty, 7);
                                } else if (typeof o[p] == "object") {
                                        initClassNames(o[p], pa + p + ".");
                                }
                        }
                }
        }
   /**
   * Return a string containing the object content. Recursive method
   * @param o Target object
   * @param d Depth. Used to generate tabs
   * @param a Array containing already dumped objects
   */

        private static function dumpToString(o, d:Number, id:Number):String {
                var ret:String = "[";
                d = d ? d : 0;
                id = id ? id : _dumped.push(new Array())-1;
                var dp:Number = d;
                var sp:String = "";
                while (d-- > 0) {
                        sp += "  ";
                }
                if (typeof o == "object") {
                        if ( o["__constructor__"][classNameProperty] ) {
                                ret += o["__constructor__"][classNameProperty] + " (" + o["__constructor__"][packNameProperty] + ")";
                        } else if (o instanceof Array) {
                                ret += "Array";
                        } else {
                                ret += "Object";
                        }
                        if (!o.__dumped) {
                                ret += "
"
+ sp;
                                o.__dumped = true;
                                _global["ASSetPropFlags"](o, "__dumped", 1);
                                _dumped[id].push(o);
                                for (var p in o) {
                                        if (typeof p != "function") {
                                                if (p.substr(0,1) != ignoreChar) {
                                                        ret += p + ": " + dumpToString(o[p], dp+1, id) + "
"
+ sp;
                                                }
                                        }
                                }
                        }
                        ret += "]";
                } else {
                        ret = o.toString();
                }
                var nd:Number = _dumped[id].length;
                while (nd-- > 0) {
                        var df = _dumped[id].pop();
                        df.__dumped = false;
                        delete df.__dumped;
                }
                return ret;
        }
        private static var _dumped:Array;
}

En prévision, pourquoi pas de pouvoir retrouver l'héritage d'une classe, comme l'a suggéré ekameleon dans le premier post de cette classe ? ;)

::Télécharger Debug 0.2::

19 août 2005

Identifiant unique pour un objet

Pour des raisons de performances, il peut etre trés utile d'attribuer un identifiant unique à un objet. En effet, un identifiant numérique peut devenir index dans un tableau, et ainsi augmenter considérablement les performances pour retrouver celui-ci sans avoir à parcourir la totalité d'un tableau.

C'est pourquoi j'ai développé une classe ObjectCollector, qui permet d'attribuer un identifiant unique à un objet, et éventuellement de retrouver cet objet à partir de ce dernier, mais uniquement s'il a été "collecté" (on a pas toujours besoin de le retrouver par son id)... ;)

/**
 * Collect objects and give them a unique Id.
 * @author  <a href="http://www.lalex.com/">LAlex</a>
 * @version 1.0
 * @since 2005-08-19
 */

class com.lalex.core.ObjectCollector {
        // Stores last given id
        private static var __id:Number = 0;
        // Store collected objects
        private static var __instances:Array = [];
        /**
        * If not already done, return a unique Id for the object
        * @param o
        * @return Object's unique id
        */

        public static function uId(o):Number {
                if (!o["__ocId"]) {
                        o["__ocId"] = ++__id;
                        _global.ASSetPropFlags(o, "__ocId", 7, 1);
                }
                return o["__ocId"];
        }
        /**
        * Collect an object. Must be call before using getById method
        * @param o Object to be collected
        * @return Object's unique id
        */

        public static function collect(o):Number {
                __instances[uId(o)] = o;
                return uId(o);
        }
        /**
        * Release an object. Must be used
        * before any delete of a collected object
        * @param o Object to be released
        */

        public static function release(o) {
                delete __instances[uId(o)];
        }
        /**
        * Release all collected objects
        */

        public static function clear() {
                var n:Number = __instances.length;
                while(n-- > 0) {
                        release(__instances[n]);
                }
        }
        /**
        * Retrieve an object from its id
        * The object must have been collected first
        * @param id Object's id
        * @see #collect
        */

        public static function getById(id:Number) {
                return __instances[id];
        }
}
Utilisation : import com.lalex.core.ObjectCollector;
// Objects
var o1 = {a:150, b:"LAlex"};
var o2 = {d:false, e:"Blablabla"};
// Collect the 'o1' object
var o1id = ObjectCollector.uId(o1);
trace("o1 id : " + o1id); // o1 id : 1
ObjectCollector.collect(o1);
trace("Retrieved ? " + (o1 == ObjectCollector.getById(o1id))); // Retrieved ? true
// Release 'o1' object
ObjectCollector.release(o1);
trace("Retrieved ? " + (o1 == ObjectCollector.getById(o1id))); // Retrieved ? false
// Get the 'o2' unique id
var o2id:Number = ObjectCollector.uId(o2);
trace("o2 id : " + o2id); // o2 id : 2
trace("Trouvé ? " + (o2 == ObjectCollector.getById(o2id)));// Retrieved ? false

::Télécharger ObjectCollector 1.0::

5 août 2005

Utilisation avancée de EventDispatcher

Je continue l'extension de classes natives de Flash pour leur attribuer le modêle évenementiel de EventDispatcher. J'en suis venu à me pencher sur la classe EventDispatcher elle-même, et j'ai découvert quelques tips, que tout le monde ne connaît pas forcément...

1. INITIALISATION
Une méthode toute simple consiste à déclarer les trois méthodes principales, et d'appeler ensuite la méthode statique 'initialize' qui va s'occuper de décorer le prototype de notre classe. A la base, c'est même dans le constructeur que l'on initialise l'instance, mais dans la classe ca fait plus mieux. En gros, on retrouve le même code un peu partout, excepté le nom de la classe qui change. import mx.events.EventDispatcher;
class EventSource {
        public var addEventListener:Function;
        public var removeEventListener:Function;
        private var dispatchEvent:Function;
        private static var __initEvent = EventDispatcher.initialize(EventSource.prototype);
}
Il faut bien avouer que à la pratique, au bout d'un moment, ca devient assez lourd de toujours retaper la même chose. Un #include serait bien pratique, mais étant donné que le nom de la classe change à chaque fois, cela obligerait à laisser le initialize() dans le code de la classe. J'ai donc utilisé une autre méthode :// fichier "initEvent.as"
private static var __dispatcher:Function = mx.events.EventDispatcher;
public function addEventListener(e:String, o) {
  __dispatcher.prototype.addEventListener.apply(this, arguments);
}
public function removeEventListener(e:String, o) {
  __dispatcher.prototype.removeEventListener.apply(this, arguments);
}
private function dispatchEvent(o) {
  __dispatcher.prototype.dispatchEvent.apply(this, arguments);
}
private function dispatchQueue(q, o) {
 __dispatcher.prototype.dispatchQueue.apply(this, arguments);
}
// fin "initEvent.as"

// fichier "EventSource.as"
class EventSource {
        #include "initEvent.as"
}
La méthode dispatchQueue() n'est jamais utilisée en général, excepté par dispatchEvent(), mais elle a besoin d'être là pour faire fonctionner le tout... Bon, je veux bien concevoir que c'est pas trés académique d'utiliser les prototypes. :$ Mais en tout cas, ca fonctionne plutôt bien, et ca me permet de chager de "dispatcher" facilement. Par exemple, si je préfère utiliser GDispatcher, il me suffit de changer la première ligne de mon fichier, et toutes les classes qui l'utilisent seront mises à jour à la compilation! 8) Et si jamais, j'ai un besoin réellement spécifique, il me suffit de copier/coller le code directement dans la classe. Cela peut même éventuellement me permettre de rajouter un typage quelqconque a mes paramètres, ou d'afficher des infos de debug, etc... 2. HANDLER INTERNE La classe EventDispatcher permet nativement de créer un handler interne qui va intercepter les évenements diffusés. Il suffit pour cela de créer une méthode qui s'appelle <nomEvenement>Handler(). Ca permet d'éviter d'inscrire une instance comme son propre écouteur :// Sans handler interne
class EventSource {
        #include "initEvent.as"
        function EventSource() {
           this.addEventListener("onEvent", this);
        }
        private function onEvent(o) {
           trace(o.type + " event dispatched");
        }
}
// Avec handler interne
class EventSource {
        #include "initEvent.as"
        function EventSource() { }
        private function onEventHandler(o) {
           trace(o.type + " event dispatched");
        }
}
Bien pratique parfois, pour du debug par exemple. :) 3. HANDLER GENERIQUE En plus d'une méthode portant le nom de l'évenement à intercepter, un objet qui en écoute un autre peut intercepter n'importe quel évenement pour peu qu'il y soit abonné, s'il implémente une méthode nommée handleEvent(). C'est d'ailleurs ce système qu'utilise la classe Delegate de Macromedia (et celle de Francis BOURRE aussi, mais son site il est plus la pour l'instant :()// Fichier "EventSource.as"
class EventSource {
        #include "initEvent.as"
        function EventSource() { }
        function doIt() {
                dispatchEvent({type:"onEvent", target:this});
        }
}
// Fin fichier
// Fichier "EventListener.as"
class EventListener {
        function EventListener() {}
        function handleEvent(o) {
                trace(o.type + " event handled by handleEvent() method");
        }
        function onEvent(o) {
                trace(o.type + " event handled by onEvent() method");
        }
}
// Fin fichier
// Code principal
var es:EventSource = new EventSource();
var el:EventListener = new EventListener();
es.addEventListener(el);
es.doIt();
// Sortie
onEvent event handled by handleEvent() method
onEvent event handled by onEvent() method
4. FONCTIONS Un évenement peut également être inetrcepté par un simple fonction. Plus besoin à ce moment là de passer par une instance de classe. Bien entendu, dans le cadre d'un développement full-OO, cette pratique n'est pas vraiment conseillée... 8|var es = new EventSource();
var fct:Function = function(o) {
        trace(o.type + " handled by a function");
}
es.addEventListener("onEvent", fct);
es.doIt();
// Sortie
onEvent handled by a function

Wala, wala pour les quelques subtilités de EventDispatcher... ;)
Bon, je sais, il faut absolument que je fasse quelque chose pour les couleurs de mon code. Ca va viendre, ca va viendre... :$

21 juillet 2005

Case hexagonales adjacentes

J'ère en ce moment sur un projet de jeu de plateau à porter en Flash. Or, ce jeu se joue avec des cases hexagonales, ce qui a priori rendait le stockage plutôt complique... :( Eh bien, il n'en est rien! 8) Il suffit en fait de stocker les cases dans un "bête" tableau à deux dimensions. C'est juste l'affichage qui diffère un peu, comme l'explique bien cette page. :)

Bien que je n'en ai pas spécialement besoin dans mon cas, je n'ai pu m'empêcher de me poser la question du pathfinding sur ce type de cartes. Eh bien, en fait c'est plutôt trés simple, étant donné que la question des diagonales ou des coins à contourner ne se pose plus. Par contre, le problême reste de trouver les cases conjointes à une case donnée. En effet, selon sur quelle "ligne" on se trouve, le test est différent.

Voici la fonction que j'ai implémenté a cette fin :function getNearHexPoints(g:Array, x:Number, y:Number):Array {
        var a:Array = new Array();
        for (var i=-1 ; i<=1 ; i++) {
                for (var j=-1 ; j<=1 ; j++) {
                        if ((i || j) && (!(i && j) || (y%2 && i == 1) || (!(y%2) && i == -1)) && g[x+i] && g[y+j]) {
                                a.push({x:x+i, y:y+j});
                        }
                }
        }
        return a;
}

Et voici son resultat (case cliquables) :

La classe PathFinder qui gère aussi les cases hexagonales ne devrait pas tarder... ;)

19 juillet 2005

Classe de debug

Je suppose que la plupart des codeurs ont leur classe de debug. Voici la mienne, a peine commencée. Il s'agit uniquement d'un dump d'un objet. La méthode un objet recursivement pour afficher l'ensemble de ses propriétés. Il gère également le fait qu'on ait pu mettre le nom de la classe dans une variable statique (par défaut __className).

/**
* Debug
* @version 0.1
* @package com.lalex.utils
*
* @author <a href="http://www.lalex.com/">LAlex</a>
* @since 2005-07-19
*/

class com.lalex.utils.Debug {
        /**
        * Property used to store class names
        */

        public static var classNameProperty:String = "__className";
        /**
        * Trace an object detailled content
        * @param o Object to display
        */

        public static function dump(o) {
                _global.trace(dumpToString(o));
        }
        /**
        * Return a string containing the object content. Recursive method
        * @param o Target object
        * @param d Depth. Used to generate tabs
        */

        private static function dumpToString(o, d:Number):String {
                d = d ? d : 0;
                var dp:Number = d;
                var sp:String = "";
                while (d-- > 0) {
                        sp += "  ";
                }
                if (typeof o == "object") {
                        var ret:String = "[";
                        if ( o["__constructor__"][classNameProperty] ) {
                                ret += o["__constructor__"][classNameProperty];
                        } else if (o instanceof Array) {
                                ret += "Array";
                        } else {
                                ret += "Object";
                        }
                        ret += "
"
+ sp;
                        for (var p in o) {
                                if (typeof p != "function") {
                                        ret += p + ": " + dumpToString(o[p], dp+1) + "
"
+ sp;
                                }
                        }
                        ret += "]";
                        return ret;
                } else {
                        return o.toString();
                }
        }
}

Une suite à cette classe est prévue, avec pitet' même une petite surprise... ;)

::Télécharger Debug01.zip::

23 juin 2005

XMath : plus de maths !

Pour mes besoins en calculs mathématiques, certaines fonctions ou optimisations m'étaient nécessaires. C'est pourquoi j'ai crée la classe XMath, qui aurait du a priori étendre la classe Math. Seulement, la classe Math étant composée uniquement de propriétes et de méthodes statiques, l'héritage se faisait mal, et m'obligeait à jongler entre les classes XMath et Math...

Mais c'était sans compter sur le __resolve que j'ai appliqué sur ma classe, me permettant ainsi de ne manipuler qu'une seule classe.

La voici. Evidemment, comme toujours, je suis ouvert à toute proposition d'amélioration! ;)
/**
 * Extended Math class
 * @class XMath
 * @version 0.1
 * @author <a href="http://www.lalex.com/">LAlex</a>
 * @since 2005-06-22
 * @about The class is dynamic to be able to use __resolve...
 */

dynamic class com.lalex.math.XMath {
        // Cosinus and sinus of round values in degree
        private static var __COS:Array = new Array();
        private static var __SIN:Array = new Array();
        // 2*PI
        // Can be used for radians modulos
        private static var __2PI:Number = 2*Math.PI;
       
        /**
        * "Simple" cosinus
        * @param n Round value in degrees
        * @return Cosinus of the degree value, with 2 decimals
        */

        public static function simpleCos(n:Number):Number {
                return __COS[n];
        }
        /**
        * "Simple" sinus
        * @param n Round value in degrees
        * @return Sinus of the degree value, with 2 decimals
        */

        public static function simpleSin(n:Number):Number {
                return __SIN[n];
        }
        /**
        * Return 2*Math.PI value
        */

        public static function get PI2():Number {
                return __2PI;
        }
        /**
        * Initilisation of the XMath static properties
        */

        private static function __init() {
                for (var i:Number = 0 ; i<360 ; i++) {
                        __COS[i] = Math.round(Math.cos(Deg2Rad(i))*100)/100;
                        __SIN[i] = Math.round(Math.sin(Deg2Rad(i))*100)/100;
                }
        }
        /**
        * Launch initialisation
        */

        private static var __initSinCos = __init();
        /**
        * Convert a degree value to radians
        * @param deg Degree value
        * @return Radian value
        */

        public static function Deg2Rad(deg:Number):Number {
                return deg*Math.PI/180;
        }
        /**
        * Convert a radian value to degrees
        * @param rad Radian value
        * @return Degree value
        */

        public static function Rad2Deg(rad:Number):Number {
                return rad*180/Math.PI;
        }
        /**
        * Return a modulo of a value
        * @param a Numeric value
        * @param mod Modulo (optional) Default 360
        * @return value >=0 and <mod
        */

        public static function modulo(a:Number, mod:Number):Number {
                if (!mod) {
                        mod = 360;
                }
                return ((a%mod+mod)%mod);
        }
        /**
        * Return a numeric value with a defined number of decimals
        * @param n Numeric value
        * @param d Number of décimals (default 0)
        * @return "Rounded" value
        */

        public static function roundTo(n:Number, d:Number):Number {
                switch(d) {
                        case 2:
                                return Math.round(n*100)/100;
                        case 1:
                                return Math.round(n*10)/10;
                        case 0:
                        case undefined:
                                return Math.round(n);
                        default:
                                var puis:Number = Math.pow(10, d);
                                return Math.round(n*puis)/puis;
                }
        }
        /**
        * Return the property or method of the Math class
        * if not found in the XMath class
        */

        private static function __resolve(p:String) {
                if (typeof Math[p] == "function")
                        return function() { return Math[p].apply(null, arguments); }
                else
                        return Math[p];
        }       
}
Utilisation :import com.lalex.math.XMath;
trace(XMath.simpleCos(120)); // 0.5
trace(XMath.cos(XMath.PI2)); //0

::Télécharger XMath01.zip::

16 juin 2005

Classe Vector : pourquoi ne pas faire la meilleure ?

De nombreux codeurs ayant besoin d'agir sur un environnement visuel ont tous à leur actif une classe Vector. De nombreuses fonctionnalité géométriques bien utiles peuvent être implémentées dans cette classe.

Mais je suis sûr que chaque codeur a implémenté quelques astuces et/ou fonctionnalités auxquelles personne n'a pensé. Ce que je propose ici, c'est que ceux qui le veulent bien nous fassent partager leur expérience à ce propos.

Voici l'interface (pas interface AS hein, juste la liste des signatures de mes méthodes) de la classe que j'utilise personnellement. Si vous avez des idées pour l'enrichir, je suis sûr que ca fera plaisir à beaucoup de monde... ;)
Vector {
        // Constructeur
        // Peut prendre deux nombres
        // ou deux points (vecteurs) qui forment un vecteur
        public function Vector(x, y);
        // Accesseurs
        public function get x():Number;
        public function set x(x:Number);
        public function get y():Number;
        public function set y(y:Number);
        // Accesseurs "virtuels"
        // Coordonnées polaires
        public function get size():Number;
        public function set size(s:Number);
        public function get angle():Number;
        public function set angle(a:Number);
        // Déplacement
        // Peut prendre deux nombres (coordonnées)
        // Ou un vecteur (utilise les coordonnées du vecteur)
        public function moveTo(x, y:Number);
        // Opérations
        public function sum(v:Vector):Void;
        public function sub(v:Vector):Void;
        public function scalar(v:Vector):Number;
        // Transformations
        public function translate(v:Vector):Void;
        public function scale(r:Number):Void;
        public function rotate(a:Number):Void;
        // Utilitaires
        // Vecteur normal (taille 1)
        public function getNormal():Vector;
        // Distance entre deux points
        public function getDistanceTo(v:Vector):Number;
        // Point d'arrivée du vecteur partant de v
        public function getEnd(v:Vector):Vector;
        // Clone
        public function clone():Vector;
        // Copie d'un autre vecteur
        public function copyFrom(v:Vector):Void;
        // Debug
        public function toString():String;
}

Ce type de classe utilise par contre une ambiguité bien pratique entre la notion de point et la notion de vecteur. En effet, un point n'est rien d'autre que l'extrémité d'un vecteur partant de l'origine... :)

Je pense créer une classe SimpleVector, qui utiliserait des angles entiers, en degrés, histoire d'optimiser les calculs... Voir un système de cache pour les coordonnées polaires...

Je pense aussi à créer une classe EventVector, qui permettrait de diffuser des évenements pour toute modification effectuée dessus...

19 octobre 2004

Super, pas vraiment synonyme de performances

Vaut-il mieux utiliser super pour surcharger une méthode sans la modifier ? Abordant le sujet avec petepx et ekameleon, c'est bien ce que je croyais, mais piqué dans ma curiosité, j'ai entrepris de faire quelques benchs.

La question est donc : si je veux accéder à une méthode héritée, vaut-il mieux la surcharger, et appeler explicitement la méthode de la classe mère avec super, ou laisser faire la parcours de la chaine de prototypes ? J'ai tout d'abord cru que lui dire où aller était bien plus rapide, étant donné que la langage n'a alors pas besoin de le chercher par lui-même ...

Mais trève de discussions, voici les classes qui m'ont servi a faire mes benchs. J'ai bien fait attention à utiliser le même nombre de lettres pour les différentes classes de même niveau, car pour ceux qui ne le savent pas encore, dans Flash, plus les noms de variables sont courts, plus ils sont efficaces en terme de performances :/** Classe mère */
class CClass {
        private static var myProp:Number = 0;
        public function testMethod() {
                myProp++;
        }
}
/** Classe fille sans surcharge */
class CChild extends CClass {
}
/** Classe fille avec surcharge */
class SChild extends CClass {
        public function testMethod() {
                super.testMethod();
        }
}
/** Lancement des benchs */
var loop:Number = 50000;
var tim:Number;
// Sans super
trace("Sans super");
var o1:CChild = new CChild();
tim = getTimer();
for (var i=0 ; i<loop ; i++) {
        o1.testMethod();
}
trace("Temps : " + (getTimer()-tim) + "ms.");
// Avec super
var o2:SChild = new SChild();
trace("Avec super");
tim = getTimer();
for (var i=0 ; i<loop ; i++) {
        o2.testMethod();
}
trace("Temps : " + (getTimer()-tim) + "ms.");
La sortie de ce code n'a pas manqué de m'étonnerSans super Temps : 1204ms. (en moyenne entre 1175 et 1225) Avec super Temps : 1535ms. (en moyenne entre 1525 et 1550) Avec une génération supplémentaire, c'est encore plus prononcé. Alors que les performances sans super ne semblent pas très affectées, celle avec super se dégradent considérablement :/** 3ème génération sans super */
class CGrandChild extends CChild {
}
/** 3ème génération avec super */
class SGrandChild extends SChild {
        public function testMethod() {
                super.testMethod();
        }
}
/** Lancement des benchs */
var loop:Number = 50000;
var tim:Number;
// Sans super
trace("Sans super");
var o1:CGrandChild = new CGrandChild();
tim = getTimer();
for (var i=0 ; i<loop ; i++) {
        o1.testMethod();
}
trace("Temps : " + (getTimer()-tim) + "ms.");
// Avec super
var o2:SGrandChild = new SGrandChild();
trace("Avec super");
tim = getTimer();
for (var i=0 ; i<loop ; i++) {
        o2.testMethod();
}
trace("Temps : " + (getTimer()-tim) + "ms.");

La sortie est plus qu'éloquente :Sans super
Temps : 1285ms. (en moyenne entre 1275 et 1305)

Avec super
Temps : 1947ms. (en moyenne entre 1925 et 1950)

Donc, il vaut mieux laisser le soin à Flash de trouver la bonne méthode plutôt que de le lui indiquer clairement. Quel manque de reconnaissance !!! :mrgreen:

PS : Par la même occasion, j'ai découvert que super.super.testMethod() n'est pas autorisé ! Encore un des mystères de super ! ;)

12 octobre 2004

Fonctions globales et ciblage

Depuis quelques temps, j'ai décidé de ne plus utiliser le this pour le ciblage de mes méthodes et propriétés à l'intérieur d'une classe. Ca paraît plutôt logique, et c'est une pratique tout à fait répandue dans d'autres langages OO. Seulement, lors du codage de mes classe d'intervalles, je me suis rendu compte que le ciblage posait quelques problèmes sur des noms de méhodes globales, renseignées dans le fichier toplevel.as ... C'est d'ailleurs pour cela qu'on retrouve des this.stop() dans ces classes.

En effet, si ma classe contient une méthode stop(), et que je l'appelle sans le this, il semble que ce soit la fonction globale qui soit appellée (en tout cas, pas celle de la classe) ... Pourtant, si je compare stop (sans ciblage) à this.stop, il me retourne bien une égalité ! 8O. Voyez plutôt :class TestStop {
        function TestStop() {
                trace("TestStop instance created");
                trace("--------------------");
        }
        function stop() {
                trace("stop method called");
        }
        function testScope() {
                stop();
                trace("Global stop ? " + (stop == _global.stop));
                trace("--------------------");
                stop.call();
                trace("Class' stop ? " + (stop == this.stop));
        }
}
/** Code de test */
var tst:TestStop = new TestStop();
tst.testScope();
/* Sortie
************
TestStop instance created
--------------------
Global stop ? false
--------------------
stop method called
Class' stop ? true
*/

Bizarre non ? :o

TimeInterval : gestion des intervalles de temps

Comme je le précisais dans mon précédent post, je suis en train de m'attaquer au redéveloppement de certaines classes fournies par Macromedia. Cela commence par la fameuse OnEnterFrameBeacon, fournissant un point d'accés central à l'évenement onEnterFrame de MovieClip. La seule différence entre ma version et celle de MM est son nom, (devenue pour l'occasion FrameBeacon, c'est bien suffisant :P), l'utilisation de EventDispatcher, et la création d'un constructeur privé, histoire d'être bien sûr qu'on ne puisse pas l'instancier. ;)

Viennent ensuite les classe de type ITimeInterval que sont MSInterval et FrameInterval.

Ces deux classes servent à gérer des intervalles de temps, soit en millisecondes, soit en nombre de frames. Elle diffuse régulièrement un évenement onTime à ses écouteurs. Le but est ici d'uniformiser l'execution d'instructions à intervalles réguliers, comme cela peut exister avec un setInterval. L'utilité de ces classes réside dans plusieurs points :

  • Une interface commune, permettant de modifier facilement à l'execution le type d'intervalle.
  • La possibilité d'executer un évenement toutes les n frames, comme c'est le cas en millisecondes avec setInterval.
  • La possiblité de définir un nombre maximum d'itérations de l'intervalle
  • La possiblité d'arrêter un intervalle en cours de route, et de le reprendre où il en était
  • La possibilité de synchroniser plusieurs instructions en leur attribuant la même instance d'un intervalle

Voici un exemple d'utilisation :import com.lalex.transitions.*;
class Diaporama {
   private var _itv:ITimeInterval;
   // Constructeur et méthodes ...
   // Si un intervalle existait déjà, on l'arrete et on le désabonne
   public function setInterval(itv:ITimeInterval) {
      if (_itv != null) {
         _itv.stop();
         _itv.removeListenerEvent("onTime", this);
      }
      _itv = itv;
      _itv.addListenerEvent("onTime", this);
   }
   public function onTime() {
      nextImage();
   }
   public function start() {
      _itv.start();
   }
}

/** Utilisation */
var diapo:Diaporama = new Diaporama();
diapo.addImage("image1.jpg");
diapo.addImage("image2.jpg");
// On crée un intervalle de 500 ms.
var ms:ITimeInterval = new MSInterval(500);
// La même instruciton pour changer d'image toutes les 20 frames
// var ms:ITimeInterval = new FrameInterval(20);
diapo.setInterval(ms);
diapo.start();

A noter que pour obtenir l'équivalent d'un onEnterFrame, il suffit de créer un FrameInterval sans paramètre (ou avec 1 en premier paramètre).

Le seul probleme important vient du fait que les destructeurs ne sont pas gérés, et qu'un delete d'une instance non stopée peut laisser tourner un processus orphelin ... :\

La classe Tween utilisant les intervalles de temps viendra prochainement ... ;)

::Télécharger TimeInterval.zip::

- page 1 de 4