BeeTimer — Documentazione Tecnica
Cooldown e loop di evento sul clock di BeeTime: one-shot, catch-up, scaled vs unscaled
📁 Panoramica
BeeTimer è l’evento a tempo. Vive in src/core/BeeTimer.js, il barrel lo
re-esporta insieme a BeeTimerClock e BEE_TIMER_DEFAULTS. BeeTime
è l’orologio; questa classe è il cooldown, lo spawn, il blink HUD, la durata di un bonus. Non è un
secondo rAF, non è un tween, non è un cronometro di rete.
BeeEngine crea un clock: this.timers = new BeeTimerClock().
Ogni frame, anche in pausa, il loop fa time.tick(timestamp) e subito
this.timers.tick(this.time). I timer sul clock avanzano da soli. Quelli costruiti a mano
(BeePlayer._boost, BeeEnemyShooter.fire) no: li ticki tu con
update.
📁 Due vie: clock del motore vs update manuale
gioco.after(duration, fn, options) e gioco.every(duration, fn, options) sono
zucchero su timers.create: iniettano clock, autoStart: true.
every forza anche loop: true. after non forza
loop: false: se passi { loop: true } ottieni un loop.
Un new BeeTimer({...}) senza clock non entra in gioco.timers. Devi
chiamare timer.update(gioco.time) (o un numero). Se lo metti in scene.update /
entity.update, in pausa del motore quell’hook non parte: l’opzione
unscaled da sola non basta. Il callback engine.update gira anche in pausa; oppure
registralo sul clock.
Non fare entrambe. Un timer già sul clock che riceve anche update a mano
avanza due volte nello stesso frame.
📁 Due assi: scaled e unscaled
Il timer non sceglie il delta da solo se gli passi un numero. Sceglie solo se gli passi
BeeTime (ha delta) o un oggetto { dt, unscaledDt }.
Argomento di update |
Timer scalato | Timer unscaled |
|---|---|---|
BeeTime (ha delta) |
time.delta(false) → dt (0 in pausa, segue timeScale)
|
time.delta(true) → unscaledDt |
oggetto { dt, unscaledDt } |
dt se è un number, altrimenti 0 |
unscaledDt se è un number, altrimenti 0 |
| numero | quello step, punto. Il flag unscaled viene ignorato |
|
unscaled e l’alias useUnscaledTime si allineano nel costruttore.
resolveStep legge timer.unscaled. Se muti solo useUnscaledTime a
caldo, lo step non cambia.
📁 One-shot vs loop
Default loop: false. I due rami in update non si mescolano.
One-shot. Quando elapsed >= duration: prima
finished = true, running = false, paused = false, poi la
callback. Nello stesso tick del clock il timer esce dalla lista live.
duration <= 0 è legale: al primo update con step ≥ 0 scatta subito (elapsed
parte da 0).
Loop. Sottrae duration, spara, ripete finché elapsed sta sotto
o raggiungi maxCatchUp (default 8, minimo 1). Dopo il tetto, il resto fa
elapsed %= duration: non perdi il residuo, perdi solo i fire oltre il cap. Se in callback
fai pause / cancel / spegni running, il catch-up di quel frame si
interrompe.
Loop con duration <= 0 lancia
Error('BeeTimer: loop richiede duration > 0') nel costruttore e in start.
In update, se muti duration a 0 a caldo, il ramo loop fa return: niente while
infinito.
📁 start, pause, resume, reset, cancel
Non sono sinonimi. Fluent: tutti ritornano this.
| Metodo | elapsed | running / paused | finished / cancelled | Clock |
|---|---|---|---|---|
start() |
azzera | true / false |
entrambi false |
re-add (idempotente) |
pause() / stop() |
invariato | false / true (no-op se non era running) |
invariati | resta in lista, update esce subito |
resume() |
invariato | true / false |
no-op se finished o cancelled |
re-add |
reset() |
azzera | non tocca running né paused | entrambi false |
non add / non remove |
cancel() |
azzera | false / false |
finished false, cancelled true |
clock.remove(this) |
start due volte è un restart, non un no-op: elapsed torna a 0. Dopo un one-shot finito,
resume non riparte: serve start() (oppure reset() +
resume()). Dopo cancel, stesso discorso: solo start (che riazzera
cancelled e re-add).
Non esiste BeeTimer.destroy. Teardown = cancel(). engine.destroy()
chiama timers.clear(), che cancella tutti i live e stacca il puntatore clock.
📁 update — chi avanza, e quando no
Guardia in testa: se non è running, o è finished, o è cancelled,
return. Poi elapsed += resolveStep(...).
Uno step non finito (NaN, undefined, oggetto senza i campi) diventa 0: il timer
non avanza. Uno step numerico negativo sottrae da elapsed (nessun clamp a 0 sul decremento). In
pausa del motore, timers.tick(this.time) parte lo stesso: i scalati sommano
dt = 0, gli unscaled sommano unscaledDt.
La callback riceve il timer: fn(this). Alias: onComplete vince su
callback se entrambi sono valorizzati. Nessun try/catch: un throw ferma il resto del
clock.tick di quel frame.
📁 BeeTimerClock — lista live, compact, create
add rifiuta i non-BeeTimer (li ritorna com’è). Imposta
timer.clock = this e fa push solo se indexOf è negativo: niente duplicati
sullo stesso clock. Non toglie il timer da un clock precedente: non spostare un’istanza
tra due clock.
tick(time) scorre la lista, chiama update, poi compatta in-place: butta i
cancelled e i one-shot finished. I loop e i timer in pausa restano.
size è #live.length dopo il compact.
Se una callback, nello stesso tick, fa gioco.after / create, il
nuovo timer viene pushato e for (i < list.length) lo vede: può ricevere lo stesso dt
nello stesso frame. Un one-shot a duration 0 creato così spara subito.
📁 Integrazione: loop, scene, entity
Ordine reale nel loop V2.4:
I timer sul clock sparano prima di scene e entity. Un after che fa
scenes.change vede ancora le entity vecchie in quello stesso frame (il change da
scene.update è un altro percorso).
BeeEnemyShooter.fire e il boost temporaneo del player sono istanze manuali:
update(engine.time) nell’entity, cancel() in destroy. Non
occupano gioco.timers. Il fire dello shooter segue la simulazione: in freeze non viene
chiamato.
📁 Esempio: simulazione vs HUD
📁 Note per chi integra
Clock per i timer di gioco/HUD. after / every se deve vivere
la pausa o non hai un hook ogni frame. Update manuale se il timer muore con l’entity.
Un numero è uno step. Per rispettare pausa e slow-mo passa gioco.time, non
gioco.time.dt.
start = restart. Utile per un cooldown che si rinfresca. Non è “parte solo se era fermo”.
Callback che lancia. Non è wrappata. Gli altri timer dello stesso tick
possono non girare; il compact del clock può restare a metà. Tieni le callback corte e senza throw.
Niente destroy sull’istanza. cancel in entity.destroy se l’hai
creato a mano. Il motore pulisce solo gioco.timers.
📁 Riepilogo API
Fluent: start, pause, resume, stop,
reset, cancel, update e i metodi del clock ritornano
this (tranne create / add, che tornano il timer). Default in
BEE_TIMER_DEFAULTS (frozen).
| Membro | Ritorna | Scopo |
|---|---|---|
BEE_TIMER_DEFAULTS |
oggetto frozen | duration 1 · loop false · unscaled false ·
autoStart false · maxCatchUp 8.
|
new BeeTimer(durationOrOptions, onComplete?, loop?, options?) |
BeeTimer |
Numero + args, oppure un oggetto spec. callback è alias di
onComplete. Throw se loop e duration <= 0.
|
duration / elapsed |
number |
Secondi. duration è mutabile a caldo (lo shooter lo riallinea a
shootInterval).
|
remaining / progress |
number |
Rimanente clamp-ato a 0. Progress in [0, 1]; se duration <= 0
vale 1. |
time |
number |
Alias deprecato di elapsed (getter/setter). |
loop / unscaled / useUnscaledTime |
boolean |
Loop vs one-shot. I due flag unscaled partono uguali; lo step legge unscaled.
|
running / paused / finished / cancelled
|
boolean |
paused è getter su campo privato: true solo dopo pause su un timer
che stava running. |
onComplete / callback |
function | null |
#fire chiama onComplete || callback con (timer). |
maxCatchUp |
number |
Tetto fire per hitch in loop. Intero ≥ 1. |
clock |
BeeTimerClock | null |
Clock owner, o null se è un timer manuale / dopo remove. |
start() |
this |
Azzera e parte. Restart se già running. Throw se loop e duration ≤ 0. |
pause() / stop() |
this |
Ferma, tiene elapsed. stop è alias di pause. |
resume() |
this |
Riparte senza azzerare. No-op se finished o cancelled. |
reset() |
this |
Azzera elapsed / finished / cancelled. Non cambia running. |
cancel() |
this |
Spegne, marca cancelled, toglie dal clock. |
update(dtOrTime) |
this |
Avanza. Accetta BeeTime, { dt, unscaledDt }, o un number (step
esplicito). |
new BeeTimerClock() |
BeeTimerClock |
Lista live vuota. Il motore ne tiene una in gioco.timers. |
clock.size |
number |
Quanti timer sono ancora in lista (paused e loop inclusi). |
clock.add(timer) |
timer |
Registra se è un BeeTimer. Dedup per identità. |
clock.create(options) |
BeeTimer |
new BeeTimer({ ...options, clock: this }). |
clock.remove(timer) |
this |
Toglie dalla lista. Se timer.clock === this, lo azzera. |
clock.tick(time) |
this |
Update + compact. Chiamato dal loop ogni frame. |
clock.clear() |
this |
Svuota, stacca, cancel su ciascuno. engine.destroy lo chiama. |
gioco.after(duration, fn, options?) |
BeeTimer |
Create + autoStart sul clock del motore. Non forza loop: false. |
gioco.every(duration, fn, options?) |
BeeTimer |
Come after, con loop: true. |
