Parlare di "real-time" in Flash/ActionScript è di rilevanza fondamentale: sia che vogliamo profilare una ruotine complessa in Papervision3D, sia che pensiamo alla temporizzazione della lettura di un feed RSS, avere codice da eseguire con una certa periodicità è caso d'uso quasi costante.
Alcune funzioni, utili a questo scopo, sono state introdotte con l'evoluzione di ActionScript e Flash Player, quando inizalmente erano pressoché inesistenti. Ciò permette oggi la gestione delle dinamiche temporali, negli ambiti applicativi più disparati.
Discostandoci dall'antichissimo approccio a 2 frame, (codice - gotoAndPlay()
frame con codice), lo scenario attuale annovera 2 approcci che permettono la temporizzazione del codice:
- l'uso del "classico" evento onEnterFrame, che si basa sul framerate dell'swf
- l'utilizzo della classe Timer, che si aggiorna secondo su un valore in millisecondi specificato nel costruttore della classe
Introdurremo il concetto di drift temporale, indicando con esso la differenza accumulata tra il valore "nominale" da noi impostato per la temporizzazione e il tempo effettivo (tick duration), computato grazie ad alcune funzioni ActionScript.
onEnterFrame
Utilizzare il frame rate come base temporale non è sempre la scelta perfetta. In questo articolo Tinic Uro, uno degli ingegneri di Adobe, svela alcuni retroscena del Flash Player e dichiara che il frame rate di un swf può fluttuare tra 5-10fps, a seconda delle risorse occupate dal sistema operativo e/o dal browser.
Un esempio di questo lo possiamo avere, immaginando il classico loop di onEnterFrame
:
import flash.utils.getTimer; //variabile di drift var cumulativeDrift = 0; //system time var lastTick = getTimer(); mc.addEventListener(Event.ENTER_FRAME, loop); function loop(e:Event):void { // FPS var fps = this.stage.frameRate; // TICK var newTick = getTimer(); var expectedTickDuration = 1000 / fps; var actualTickDuration = newTick - lastTick; lastTick = newTick; // DRIFT var tickDrift = actualTickDuration - expectedTickDuration; cumulativeDrift += tickDrift; var cumulativeDriftSeconds = cumulativeDrift / 1000; trace("drift=", cumulativeDrift, "fps=", fps, "actualTickDuration=", actualTickDuration); }
La classe Timer
L'introduzione della classe Timer
in ActionScript 3, ha rimpiazzato il vecchio metodo setInterval
. Nel costruttore possiamo specificare l'intervallo temporale richiesto e, grazie al consueto addEventListener
, una funzione da invocare all'evento di temporizzazione (o "tick event").
Come rileva l'esempio a seguire, anche questo modo non è immune, tra un intervallo e l'altro, da drift temporale. Ecco il codice per il test:
import flash.utils.getTimer; import flash.utils.Timer; //intervallo in millisecondi var time:int=50; //variabile di drift var cumulativeDrift=0; //system time var lastTick=getTimer(); var timer:Timer=new Timer(time); timer.addEventListener( TimerEvent.TIMER, onTick); timer.start(); function onTick( event:TimerEvent ):void { // FPS var fps= time; // TICK var newTick=getTimer(); var expectedTickDuration=time; var actualTickDuration=newTick-lastTick; lastTick = newTick; // DRIFT var tickDrift=actualTickDuration-expectedTickDuration; cumulativeDrift+=tickDrift; var cumulativeDriftSeconds=cumulativeDrift/1000; trace("drift=", cumulativeDrift, "time=", time, "actualTickDuration=", actualTickDuration); }
TimeKeeper, una soluzione migliore ...
Nel sito www.computus.org, John Dalziel esplora varie tematiche correlate alla computazione temporale. Come astronomo nonchè sviluppatore Flash, John ha sviluppato una più efficiente soluzione al problema di a-periodicità nel Flash Player (altresì detto "isocronismo"), creando una classe TimeKeeper
e il relativo TimekeeperEvent
che possono convenientemente essere usati nelle nostre applicazioni.
La classe TimeKeeper
dispone di un regolatore interno, implementato con classe Timer, operante alla frequenza di 20 volte al secondo: l'accuratezza nel calcolo del periodo è mantenuta da un accumulatore basato sul valore via via dato dal "clock" del regolatore: quando il valore dell'accumulatore supera la frequenza impostata (cioè la temporizzazione da noi richiesta) viene generato e propagato un "tick event".
Osservando più in dettaglio la classe TimeKeeper, ne individuiamo il punto fondamentale:
private function onTimerEvent( e:TimerEvent ):void { var regulatorNew:Number = getTimer() // rileva time di sistema var regulatorDelta:Number = regulatorNew - regulatorCache // calcola la differenza dall'ultimo tick regulatorAcc += regulatorDelta // incrementa accumulatore if ( regulatorAcc > _tickFrequency ) // se valore accumulatore supera frequenza di tick desiderata... { // imposta il valore del temporizzatore come tempo iniziale + durata del tick e propaga un tick event if ( _isTicking == true ) { value = time + _tickDuration } // reset accumulator regulatorAcc -= _tickFrequency // reset accumulatore } regulatorCache = regulatorNew // cache previous regulator value }
Il time base delle computazioni successive viene calcolato con un new Date().valueOf()
.
Riferimenti: