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)
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2022-08-04 18:04:10 +02:00
|
|
|
*
|
|
|
|
* This is "public" to TypeScript, because it's a tool for performance measurements.
|
|
|
|
* Consider this as experimental, and do not rely on it always being here in future releases.
|
2022-07-29 17:59:39 +02:00
|
|
|
*/
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|