2021-06-18 13:05:06 +02:00
type ICallback = ( t : number ) = > void
2022-05-25 00:37:18 +02:00
function createRafTicker() {
const ticker = new Ticker ( )
if ( typeof window !== 'undefined' ) {
/ * *
* @remarks
* TODO users should also be able to define their own ticker .
* /
const onAnimationFrame = ( t : number ) = > {
ticker . tick ( t )
window . requestAnimationFrame ( onAnimationFrame )
}
window . requestAnimationFrame ( onAnimationFrame )
} else {
ticker . tick ( 0 )
setTimeout ( ( ) = > ticker . tick ( 1 ) , 0 )
console . log (
` @theatre/dataverse is running in a server rather than in a browser. We haven't gotten around to testing server-side rendering, so if something is working in the browser but not on the server, please file a bug: https://github.com/theatre-js/theatre/issues/new ` ,
)
}
return ticker
}
let rafTicker : undefined | Ticker
2022-01-19 13:06:13 +01:00
/ * *
* The Ticker class helps schedule callbacks . Scheduled callbacks are executed per tick . Ticks can be triggered by an
* external scheduling strategy , e . g . a raf .
* /
2021-06-18 13:05:06 +02:00
export default class Ticker {
2022-07-16 15:47:31 +02:00
/** Get a shared `requestAnimationFrame` ticker. */
2022-05-25 00:37:18 +02:00
static get raf ( ) : Ticker {
if ( ! rafTicker ) {
rafTicker = createRafTicker ( )
}
return rafTicker
}
2021-06-18 13:05:06 +02:00
private _scheduledForThisOrNextTick : Set < ICallback >
private _scheduledForNextTick : Set < ICallback >
private _timeAtCurrentTick : number
private _ticking : boolean = false
2022-07-29 17:59:39 +02:00
/ * *
* Counts up for every tick executed .
* Internally , this is used to measure ticks per second .
* /
public __ticks = 0
2021-06-18 13:05:06 +02:00
constructor ( ) {
this . _scheduledForThisOrNextTick = new Set ( )
this . _scheduledForNextTick = new Set ( )
this . _timeAtCurrentTick = 0
}
/ * *
* Registers for fn to be called either on this tick or the next tick .
*
2022-01-19 13:06:13 +01:00
* If ` onThisOrNextTick() ` is called while ` Ticker.tick() ` is running , the
2021-06-18 13:05:06 +02:00
* side effect _will_ be called within the running tick . If you don ' t want this
2022-01-19 13:06:13 +01:00
* behavior , you can use ` onNextTick() ` .
2021-06-18 13:05:06 +02:00
*
2022-01-19 13:06:13 +01:00
* Note that ` fn ` will be added to a ` Set() ` . Which means , if you call ` onThisOrNextTick(fn) `
2021-06-18 13:05:06 +02:00
* with the same fn twice in a single tick , it ' ll only run once .
2022-01-19 13:06:13 +01:00
*
2022-02-23 22:53:39 +01:00
* @param fn - The function to be registered .
2022-01-19 13:06:13 +01:00
*
* @see offThisOrNextTick
2021-06-18 13:05:06 +02:00
* /
onThisOrNextTick ( fn : ICallback ) {
this . _scheduledForThisOrNextTick . add ( fn )
}
/ * *
* Registers a side effect to be called on the next tick .
*
2022-02-23 22:53:39 +01:00
* @param fn - The function to be registered .
2022-01-19 13:06:13 +01:00
*
* @see onThisOrNextTick
* @see offNextTick
2021-06-18 13:05:06 +02:00
* /
onNextTick ( fn : ICallback ) {
this . _scheduledForNextTick . add ( fn )
}
2022-01-19 13:06:13 +01:00
/ * *
* De - registers a fn to be called either on this tick or the next tick .
*
2022-02-23 22:53:39 +01:00
* @param fn - The function to be de - registered .
2022-01-19 13:06:13 +01:00
*
* @see onThisOrNextTick
* /
2021-06-18 13:05:06 +02:00
offThisOrNextTick ( fn : ICallback ) {
this . _scheduledForThisOrNextTick . delete ( fn )
}
2022-01-19 13:06:13 +01:00
/ * *
* De - registers a fn to be called on the next tick .
*
2022-02-23 22:53:39 +01:00
* @param fn - The function to be de - registered .
2022-01-19 13:06:13 +01:00
*
* @see onNextTick
* /
2021-06-18 13:05:06 +02:00
offNextTick ( fn : ICallback ) {
this . _scheduledForNextTick . delete ( fn )
}
2022-01-19 13:06:13 +01:00
/ * *
* The time at the start of the current tick if there is a tick in progress , otherwise defaults to
* ` performance.now() ` .
* /
2021-06-18 13:05:06 +02:00
get time() {
if ( this . _ticking ) {
return this . _timeAtCurrentTick
} else return performance . now ( )
}
2022-01-19 13:06:13 +01:00
/ * *
* Triggers a tick which starts executing the callbacks scheduled for this tick .
*
2022-02-23 22:53:39 +01:00
* @param t - The time at the tick .
2022-01-19 13:06:13 +01:00
*
* @see onThisOrNextTick
* @see onNextTick
* /
2021-06-18 13:05:06 +02:00
tick ( t : number = performance . now ( ) ) {
2022-08-01 20:18:07 +02:00
if ( process . env . NODE_ENV === 'development' ) {
if ( ! ( this instanceof Ticker ) ) {
throw new Error (
'ticker.tick must be called while bound to the ticker. As in, "ticker.tick(time)" or "requestAnimationFrame((t) => ticker.tick(t))" for performance.' ,
)
}
}
2022-07-29 17:59:39 +02:00
this . __ticks ++
2021-06-18 13:05:06 +02:00
this . _ticking = true
this . _timeAtCurrentTick = t
2022-06-09 19:12:40 +02:00
for ( const v of this . _scheduledForNextTick ) {
this . _scheduledForThisOrNextTick . add ( v )
}
2021-06-18 13:05:06 +02:00
this . _scheduledForNextTick . clear ( )
this . _tick ( 0 )
this . _ticking = false
}
private _tick ( iterationNumber : number ) : void {
const time = this . time
if ( iterationNumber > 10 ) {
console . warn ( '_tick() recursing for 10 times' )
}
if ( iterationNumber > 100 ) {
throw new Error ( ` Maximum recursion limit for _tick() ` )
}
const oldSet = this . _scheduledForThisOrNextTick
this . _scheduledForThisOrNextTick = new Set ( )
2022-06-09 19:12:40 +02:00
for ( const fn of oldSet ) {
2021-06-18 13:05:06 +02:00
fn ( time )
2022-06-09 19:12:40 +02:00
}
2021-06-18 13:05:06 +02:00
if ( this . _scheduledForThisOrNextTick . size > 0 ) {
return this . _tick ( iterationNumber + 1 )
}
}
}