@armniko/ticker
Advanced tools
@@ -8,3 +8,7 @@ import { Time } from '../types'; | ||
| private _msSinceLastCalculation; | ||
| private _lowFpsStreakThreshold; | ||
| private _recoveryStreakThreshold; | ||
| private _lowFpsInRow; | ||
| private _highFpsInRow; | ||
| private _isLowFps; | ||
| private _lowFpsCallback?; | ||
@@ -11,0 +15,0 @@ currentFps(): number; |
+99
-54
@@ -8,3 +8,7 @@ //#region src/fps/fps.ts | ||
| _msSinceLastCalculation = 0; | ||
| _lowFpsStreakThreshold = 5; | ||
| _recoveryStreakThreshold = 5; | ||
| _lowFpsInRow = 0; | ||
| _highFpsInRow = 0; | ||
| _isLowFps = false; | ||
| _lowFpsCallback; | ||
@@ -16,3 +20,3 @@ currentFps() { | ||
| this.collectFps(time); | ||
| this._msSinceLastCalculation += time.deltaMs; | ||
| this._msSinceLastCalculation += time.unscaledDeltaMs; | ||
| if (this._msSinceLastCalculation >= this._calculateFpsEachMs) { | ||
@@ -32,3 +36,3 @@ this._msSinceLastCalculation = 0; | ||
| collectFps(time) { | ||
| this._lastChecks.push(Math.round(1e3 / time.deltaMs)); | ||
| this._lastChecks.push(Math.round(1e3 / time.unscaledDeltaMs)); | ||
| } | ||
@@ -41,8 +45,18 @@ calculateAverageFps() { | ||
| checkAndNotifyLowFps() { | ||
| if (this._minFps && this._currentFps < this._minFps) { | ||
| if (this._minFps) if (this._currentFps < this._minFps) { | ||
| this._highFpsInRow = 0; | ||
| this._lowFpsInRow++; | ||
| if (this._lowFpsInRow === 5) { | ||
| this._lowFpsInRow = 0; | ||
| if (this._lowFpsCallback) this._lowFpsCallback(); | ||
| if (this._lowFpsInRow >= this._lowFpsStreakThreshold && !this._isLowFps) { | ||
| this._isLowFps = true; | ||
| this._lowFpsCallback?.(); | ||
| } | ||
| } else { | ||
| this._lowFpsInRow = 0; | ||
| if (this._isLowFps) { | ||
| this._highFpsInRow++; | ||
| if (this._highFpsInRow >= this._recoveryStreakThreshold) { | ||
| this._highFpsInRow = 0; | ||
| this._isLowFps = false; | ||
| } | ||
| } | ||
| } | ||
@@ -55,19 +69,45 @@ } | ||
| _fps = new Fps(); | ||
| _drawQueue = []; | ||
| _logicQueue = []; | ||
| _lowFpsQueue = []; | ||
| _drawQueue = /* @__PURE__ */ new Map(); | ||
| _logicQueue = /* @__PURE__ */ new Map(); | ||
| _lowFpsQueue = /* @__PURE__ */ new Map(); | ||
| _maxFps; | ||
| _expectedFps; | ||
| _timeScale = 1; | ||
| _time = { | ||
| delta: 0, | ||
| deltaMs: 0 | ||
| deltaMs: 0, | ||
| unscaledDelta: 0, | ||
| unscaledDeltaMs: 0 | ||
| }; | ||
| _lastTimestamp = 0; | ||
| _logicTicksAccumulatedMs = 0; | ||
| _maxAccumulatedMs = 250; | ||
| _isStarted = false; | ||
| _requestFrameId; | ||
| _nextTaskId = 0; | ||
| _loop = (currentTimestamp) => { | ||
| if (this._isStarted) { | ||
| const maxDeltaMs = 1e3 / this._maxFps; | ||
| let unscaledDeltaMs = currentTimestamp - this._lastTimestamp; | ||
| if (unscaledDeltaMs >= maxDeltaMs) { | ||
| if (unscaledDeltaMs > this._maxAccumulatedMs) unscaledDeltaMs = this._maxAccumulatedMs; | ||
| const scaledDeltaMs = unscaledDeltaMs * this._timeScale; | ||
| this._time = { | ||
| delta: scaledDeltaMs / 1e3, | ||
| deltaMs: scaledDeltaMs, | ||
| unscaledDelta: unscaledDeltaMs / 1e3, | ||
| unscaledDeltaMs | ||
| }; | ||
| this._lastTimestamp = currentTimestamp; | ||
| this._fps.calculate(this._time); | ||
| this.runQueue(); | ||
| } | ||
| this._requestFrameId = window.requestAnimationFrame(this._loop); | ||
| } | ||
| }; | ||
| constructor(options) { | ||
| this.setFps({ | ||
| min: options?.minFps || 0, | ||
| max: options?.maxFps || 60, | ||
| expected: options?.expectedFps || 60 | ||
| min: options?.minFps ?? 0, | ||
| max: options?.maxFps ?? 60, | ||
| expected: options?.expectedFps ?? 60 | ||
| }); | ||
@@ -82,7 +122,11 @@ if (options?.onDrawTick) this.addDrawTask(options.onDrawTick); | ||
| start() { | ||
| this._lastTimestamp = performance.now(); | ||
| this.loop(this._lastTimestamp); | ||
| if (!this._isStarted) { | ||
| this._isStarted = true; | ||
| this._lastTimestamp = performance.now(); | ||
| this._loop(this._lastTimestamp); | ||
| } | ||
| } | ||
| stop() { | ||
| if (this._requestFrameId) { | ||
| this._isStarted = false; | ||
| if (this._requestFrameId !== void 0) { | ||
| window.cancelAnimationFrame(this._requestFrameId); | ||
@@ -93,3 +137,3 @@ this._requestFrameId = void 0; | ||
| isStarted() { | ||
| return !!this._requestFrameId; | ||
| return this._isStarted; | ||
| } | ||
@@ -102,5 +146,11 @@ setFps(options) { | ||
| } | ||
| setTimeScale(scale) { | ||
| this._timeScale = Math.max(0, scale); | ||
| return this; | ||
| } | ||
| addDrawTask(callback) { | ||
| const id = this._nextTaskId++; | ||
| this._drawQueue.set(id, { callback }); | ||
| return { | ||
| index: this._drawQueue.push({ callback }) - 1, | ||
| ref: id, | ||
| type: "draw" | ||
@@ -110,4 +160,6 @@ }; | ||
| addLogicTask(callback) { | ||
| const id = this._nextTaskId++; | ||
| this._logicQueue.set(id, { callback }); | ||
| return { | ||
| index: this._logicQueue.push({ callback }) - 1, | ||
| ref: id, | ||
| type: "logic" | ||
@@ -117,4 +169,6 @@ }; | ||
| addLowFpsTask(callback) { | ||
| const id = this._nextTaskId++; | ||
| this._lowFpsQueue.set(id, { callback }); | ||
| return { | ||
| index: this._lowFpsQueue.push({ callback }) - 1, | ||
| ref: id, | ||
| type: "low-fps" | ||
@@ -126,9 +180,9 @@ }; | ||
| case "draw": | ||
| this._drawQueue.splice(taskId.index, 1); | ||
| this._drawQueue.delete(taskId.ref); | ||
| break; | ||
| case "logic": | ||
| this._logicQueue.splice(taskId.index, 1); | ||
| this._logicQueue.delete(taskId.ref); | ||
| break; | ||
| case "low-fps": | ||
| this._lowFpsQueue.splice(taskId.index, 1); | ||
| this._lowFpsQueue.delete(taskId.ref); | ||
| break; | ||
@@ -140,2 +194,5 @@ } | ||
| } | ||
| timeScale() { | ||
| return this._timeScale; | ||
| } | ||
| /** | ||
@@ -145,3 +202,3 @@ * @deprecated Use `time.deltaMs` provided in draw/logic task callbacks instead. | ||
| msBetweenTicks() { | ||
| return this._time.deltaMs; | ||
| return this._time.unscaledDeltaMs; | ||
| } | ||
@@ -152,21 +209,7 @@ /** | ||
| ticksMissed() { | ||
| return this._time.deltaMs / (1e3 / this._expectedFps); | ||
| return this._time.unscaledDeltaMs / (1e3 / this._expectedFps); | ||
| } | ||
| loop(currentTimestamp) { | ||
| const maxDeltaMs = 1e3 / this._maxFps; | ||
| const deltaMs = currentTimestamp - this._lastTimestamp; | ||
| if (deltaMs >= maxDeltaMs) { | ||
| this._time = { | ||
| delta: deltaMs / 1e3, | ||
| deltaMs | ||
| }; | ||
| this._lastTimestamp = currentTimestamp; | ||
| this._fps.calculate(this._time); | ||
| this.runQueue(); | ||
| } | ||
| this._requestFrameId = window.requestAnimationFrame(this.loop.bind(this)); | ||
| } | ||
| runQueue() { | ||
| if (this._logicQueue.length > 0) this.runLogicQueue(); | ||
| if (this._drawQueue.length > 0) this.runDrawQueue(); | ||
| if (this._logicQueue.size > 0) this.runLogicQueue(); | ||
| if (this._drawQueue.size > 0) this.runDrawQueue(); | ||
| } | ||
@@ -178,3 +221,5 @@ runLogicQueue() { | ||
| delta: expectedDeltaMs / 1e3, | ||
| deltaMs: expectedDeltaMs | ||
| deltaMs: expectedDeltaMs, | ||
| unscaledDelta: expectedDeltaMs / 1e3, | ||
| unscaledDeltaMs: expectedDeltaMs | ||
| }; | ||
@@ -196,11 +241,17 @@ while (this._logicTicksAccumulatedMs >= expectedDeltaMs) { | ||
| var TickerMock = class extends Ticker { | ||
| _isStarted = false; | ||
| _isStartedMock = false; | ||
| _defaultTimeMock = { | ||
| delta: 1 / this.fps(), | ||
| deltaMs: 1e3 / this.fps(), | ||
| unscaledDelta: 1 / this.fps(), | ||
| unscaledDeltaMs: 1e3 / this.fps() | ||
| }; | ||
| start() { | ||
| this._isStarted = true; | ||
| this._isStartedMock = true; | ||
| } | ||
| stop() { | ||
| this._isStarted = false; | ||
| this._isStartedMock = false; | ||
| } | ||
| isStarted() { | ||
| return this._isStarted; | ||
| return this._isStartedMock; | ||
| } | ||
@@ -217,12 +268,6 @@ fps() { | ||
| simulateLogicTick(time = void 0) { | ||
| this._logicQueue.forEach((item) => item.callback(time || { | ||
| delta: 1 / this.fps(), | ||
| deltaMs: 1e3 / this.fps() | ||
| })); | ||
| this._logicQueue.forEach((item) => item.callback(time || this._defaultTimeMock)); | ||
| } | ||
| simulateDrawTick(time = void 0) { | ||
| this._drawQueue.forEach((item) => item.callback(time || { | ||
| delta: 1 / this.fps(), | ||
| deltaMs: 1e3 / this.fps() | ||
| })); | ||
| this._drawQueue.forEach((item) => item.callback(time || this._defaultTimeMock)); | ||
| } | ||
@@ -229,0 +274,0 @@ simulateLowFps() { |
+99
-54
@@ -12,3 +12,7 @@ (function(global, factory) { | ||
| _msSinceLastCalculation = 0; | ||
| _lowFpsStreakThreshold = 5; | ||
| _recoveryStreakThreshold = 5; | ||
| _lowFpsInRow = 0; | ||
| _highFpsInRow = 0; | ||
| _isLowFps = false; | ||
| _lowFpsCallback; | ||
@@ -20,3 +24,3 @@ currentFps() { | ||
| this.collectFps(time); | ||
| this._msSinceLastCalculation += time.deltaMs; | ||
| this._msSinceLastCalculation += time.unscaledDeltaMs; | ||
| if (this._msSinceLastCalculation >= this._calculateFpsEachMs) { | ||
@@ -36,3 +40,3 @@ this._msSinceLastCalculation = 0; | ||
| collectFps(time) { | ||
| this._lastChecks.push(Math.round(1e3 / time.deltaMs)); | ||
| this._lastChecks.push(Math.round(1e3 / time.unscaledDeltaMs)); | ||
| } | ||
@@ -45,8 +49,18 @@ calculateAverageFps() { | ||
| checkAndNotifyLowFps() { | ||
| if (this._minFps && this._currentFps < this._minFps) { | ||
| if (this._minFps) if (this._currentFps < this._minFps) { | ||
| this._highFpsInRow = 0; | ||
| this._lowFpsInRow++; | ||
| if (this._lowFpsInRow === 5) { | ||
| this._lowFpsInRow = 0; | ||
| if (this._lowFpsCallback) this._lowFpsCallback(); | ||
| if (this._lowFpsInRow >= this._lowFpsStreakThreshold && !this._isLowFps) { | ||
| this._isLowFps = true; | ||
| this._lowFpsCallback?.(); | ||
| } | ||
| } else { | ||
| this._lowFpsInRow = 0; | ||
| if (this._isLowFps) { | ||
| this._highFpsInRow++; | ||
| if (this._highFpsInRow >= this._recoveryStreakThreshold) { | ||
| this._highFpsInRow = 0; | ||
| this._isLowFps = false; | ||
| } | ||
| } | ||
| } | ||
@@ -59,19 +73,45 @@ } | ||
| _fps = new Fps(); | ||
| _drawQueue = []; | ||
| _logicQueue = []; | ||
| _lowFpsQueue = []; | ||
| _drawQueue = /* @__PURE__ */ new Map(); | ||
| _logicQueue = /* @__PURE__ */ new Map(); | ||
| _lowFpsQueue = /* @__PURE__ */ new Map(); | ||
| _maxFps; | ||
| _expectedFps; | ||
| _timeScale = 1; | ||
| _time = { | ||
| delta: 0, | ||
| deltaMs: 0 | ||
| deltaMs: 0, | ||
| unscaledDelta: 0, | ||
| unscaledDeltaMs: 0 | ||
| }; | ||
| _lastTimestamp = 0; | ||
| _logicTicksAccumulatedMs = 0; | ||
| _maxAccumulatedMs = 250; | ||
| _isStarted = false; | ||
| _requestFrameId; | ||
| _nextTaskId = 0; | ||
| _loop = (currentTimestamp) => { | ||
| if (this._isStarted) { | ||
| const maxDeltaMs = 1e3 / this._maxFps; | ||
| let unscaledDeltaMs = currentTimestamp - this._lastTimestamp; | ||
| if (unscaledDeltaMs >= maxDeltaMs) { | ||
| if (unscaledDeltaMs > this._maxAccumulatedMs) unscaledDeltaMs = this._maxAccumulatedMs; | ||
| const scaledDeltaMs = unscaledDeltaMs * this._timeScale; | ||
| this._time = { | ||
| delta: scaledDeltaMs / 1e3, | ||
| deltaMs: scaledDeltaMs, | ||
| unscaledDelta: unscaledDeltaMs / 1e3, | ||
| unscaledDeltaMs | ||
| }; | ||
| this._lastTimestamp = currentTimestamp; | ||
| this._fps.calculate(this._time); | ||
| this.runQueue(); | ||
| } | ||
| this._requestFrameId = window.requestAnimationFrame(this._loop); | ||
| } | ||
| }; | ||
| constructor(options) { | ||
| this.setFps({ | ||
| min: options?.minFps || 0, | ||
| max: options?.maxFps || 60, | ||
| expected: options?.expectedFps || 60 | ||
| min: options?.minFps ?? 0, | ||
| max: options?.maxFps ?? 60, | ||
| expected: options?.expectedFps ?? 60 | ||
| }); | ||
@@ -86,7 +126,11 @@ if (options?.onDrawTick) this.addDrawTask(options.onDrawTick); | ||
| start() { | ||
| this._lastTimestamp = performance.now(); | ||
| this.loop(this._lastTimestamp); | ||
| if (!this._isStarted) { | ||
| this._isStarted = true; | ||
| this._lastTimestamp = performance.now(); | ||
| this._loop(this._lastTimestamp); | ||
| } | ||
| } | ||
| stop() { | ||
| if (this._requestFrameId) { | ||
| this._isStarted = false; | ||
| if (this._requestFrameId !== void 0) { | ||
| window.cancelAnimationFrame(this._requestFrameId); | ||
@@ -97,3 +141,3 @@ this._requestFrameId = void 0; | ||
| isStarted() { | ||
| return !!this._requestFrameId; | ||
| return this._isStarted; | ||
| } | ||
@@ -106,5 +150,11 @@ setFps(options) { | ||
| } | ||
| setTimeScale(scale) { | ||
| this._timeScale = Math.max(0, scale); | ||
| return this; | ||
| } | ||
| addDrawTask(callback) { | ||
| const id = this._nextTaskId++; | ||
| this._drawQueue.set(id, { callback }); | ||
| return { | ||
| index: this._drawQueue.push({ callback }) - 1, | ||
| ref: id, | ||
| type: "draw" | ||
@@ -114,4 +164,6 @@ }; | ||
| addLogicTask(callback) { | ||
| const id = this._nextTaskId++; | ||
| this._logicQueue.set(id, { callback }); | ||
| return { | ||
| index: this._logicQueue.push({ callback }) - 1, | ||
| ref: id, | ||
| type: "logic" | ||
@@ -121,4 +173,6 @@ }; | ||
| addLowFpsTask(callback) { | ||
| const id = this._nextTaskId++; | ||
| this._lowFpsQueue.set(id, { callback }); | ||
| return { | ||
| index: this._lowFpsQueue.push({ callback }) - 1, | ||
| ref: id, | ||
| type: "low-fps" | ||
@@ -130,9 +184,9 @@ }; | ||
| case "draw": | ||
| this._drawQueue.splice(taskId.index, 1); | ||
| this._drawQueue.delete(taskId.ref); | ||
| break; | ||
| case "logic": | ||
| this._logicQueue.splice(taskId.index, 1); | ||
| this._logicQueue.delete(taskId.ref); | ||
| break; | ||
| case "low-fps": | ||
| this._lowFpsQueue.splice(taskId.index, 1); | ||
| this._lowFpsQueue.delete(taskId.ref); | ||
| break; | ||
@@ -144,2 +198,5 @@ } | ||
| } | ||
| timeScale() { | ||
| return this._timeScale; | ||
| } | ||
| /** | ||
@@ -149,3 +206,3 @@ * @deprecated Use `time.deltaMs` provided in draw/logic task callbacks instead. | ||
| msBetweenTicks() { | ||
| return this._time.deltaMs; | ||
| return this._time.unscaledDeltaMs; | ||
| } | ||
@@ -156,21 +213,7 @@ /** | ||
| ticksMissed() { | ||
| return this._time.deltaMs / (1e3 / this._expectedFps); | ||
| return this._time.unscaledDeltaMs / (1e3 / this._expectedFps); | ||
| } | ||
| loop(currentTimestamp) { | ||
| const maxDeltaMs = 1e3 / this._maxFps; | ||
| const deltaMs = currentTimestamp - this._lastTimestamp; | ||
| if (deltaMs >= maxDeltaMs) { | ||
| this._time = { | ||
| delta: deltaMs / 1e3, | ||
| deltaMs | ||
| }; | ||
| this._lastTimestamp = currentTimestamp; | ||
| this._fps.calculate(this._time); | ||
| this.runQueue(); | ||
| } | ||
| this._requestFrameId = window.requestAnimationFrame(this.loop.bind(this)); | ||
| } | ||
| runQueue() { | ||
| if (this._logicQueue.length > 0) this.runLogicQueue(); | ||
| if (this._drawQueue.length > 0) this.runDrawQueue(); | ||
| if (this._logicQueue.size > 0) this.runLogicQueue(); | ||
| if (this._drawQueue.size > 0) this.runDrawQueue(); | ||
| } | ||
@@ -182,3 +225,5 @@ runLogicQueue() { | ||
| delta: expectedDeltaMs / 1e3, | ||
| deltaMs: expectedDeltaMs | ||
| deltaMs: expectedDeltaMs, | ||
| unscaledDelta: expectedDeltaMs / 1e3, | ||
| unscaledDeltaMs: expectedDeltaMs | ||
| }; | ||
@@ -200,11 +245,17 @@ while (this._logicTicksAccumulatedMs >= expectedDeltaMs) { | ||
| var TickerMock = class extends Ticker { | ||
| _isStarted = false; | ||
| _isStartedMock = false; | ||
| _defaultTimeMock = { | ||
| delta: 1 / this.fps(), | ||
| deltaMs: 1e3 / this.fps(), | ||
| unscaledDelta: 1 / this.fps(), | ||
| unscaledDeltaMs: 1e3 / this.fps() | ||
| }; | ||
| start() { | ||
| this._isStarted = true; | ||
| this._isStartedMock = true; | ||
| } | ||
| stop() { | ||
| this._isStarted = false; | ||
| this._isStartedMock = false; | ||
| } | ||
| isStarted() { | ||
| return this._isStarted; | ||
| return this._isStartedMock; | ||
| } | ||
@@ -221,12 +272,6 @@ fps() { | ||
| simulateLogicTick(time = void 0) { | ||
| this._logicQueue.forEach((item) => item.callback(time || { | ||
| delta: 1 / this.fps(), | ||
| deltaMs: 1e3 / this.fps() | ||
| })); | ||
| this._logicQueue.forEach((item) => item.callback(time || this._defaultTimeMock)); | ||
| } | ||
| simulateDrawTick(time = void 0) { | ||
| this._drawQueue.forEach((item) => item.callback(time || { | ||
| delta: 1 / this.fps(), | ||
| deltaMs: 1e3 / this.fps() | ||
| })); | ||
| this._drawQueue.forEach((item) => item.callback(time || this._defaultTimeMock)); | ||
| } | ||
@@ -233,0 +278,0 @@ simulateLowFps() { |
| import { Time } from '../types'; | ||
| import { Ticker } from './ticker'; | ||
| export declare class TickerMock extends Ticker { | ||
| private _isStarted; | ||
| private _isStartedMock; | ||
| private _defaultTimeMock; | ||
| start(): void; | ||
@@ -6,0 +7,0 @@ stop(): void; |
| import { LowFpsQueueItem, LogicQueueItem, DrawQueueItem, TickerTaskId, TickerOptions } from '../types'; | ||
| export declare class Ticker { | ||
| private readonly _fps; | ||
| private readonly _drawQueue; | ||
| private readonly _logicQueue; | ||
| private readonly _lowFpsQueue; | ||
| private _drawQueue; | ||
| private _logicQueue; | ||
| private _lowFpsQueue; | ||
| private _maxFps; | ||
| private _expectedFps; | ||
| private _timeScale; | ||
| private _time; | ||
| private _lastTimestamp; | ||
| private _logicTicksAccumulatedMs; | ||
| private _maxAccumulatedMs; | ||
| private _isStarted; | ||
| private _requestFrameId?; | ||
| private _nextTaskId; | ||
| private _loop; | ||
| constructor(options?: TickerOptions); | ||
@@ -22,2 +27,3 @@ start(): void; | ||
| }): this; | ||
| setTimeScale(scale: number): this; | ||
| addDrawTask(callback: DrawQueueItem['callback']): TickerTaskId; | ||
@@ -28,2 +34,3 @@ addLogicTask(callback: LogicQueueItem['callback']): TickerTaskId; | ||
| fps(): number; | ||
| timeScale(): number; | ||
| /** | ||
@@ -37,3 +44,2 @@ * @deprecated Use `time.deltaMs` provided in draw/logic task callbacks instead. | ||
| ticksMissed(): number; | ||
| private loop; | ||
| private runQueue; | ||
@@ -40,0 +46,0 @@ private runLogicQueue; |
+3
-1
@@ -37,3 +37,3 @@ export interface DrawQueueItem { | ||
| export interface TickerTaskId { | ||
| index: number; | ||
| ref: number; | ||
| type: 'draw' | 'logic' | 'low-fps'; | ||
@@ -44,2 +44,4 @@ } | ||
| deltaMs: number; | ||
| unscaledDelta: number; | ||
| unscaledDeltaMs: number; | ||
| } |
+4
-2
| { | ||
| "name": "@armniko/ticker", | ||
| "version": "2.2.0", | ||
| "description": "A lightweight, zero-dependency JavaScript/TypeScript library for running an application loop with separate logic and draw ticks, FPS limiting, and low-FPS detection.", | ||
| "version": "2.3.0", | ||
| "description": "A lightweight, zero-dependency JavaScript/TypeScript library for running an application loop with separate logic and draw ticks, time scaling, FPS limiting, and low-FPS detection.", | ||
| "author": "ArmΔ«ns Nikolajevs <armins.nikolajevs@gmail.com>", | ||
@@ -15,2 +15,3 @@ "license": "MIT", | ||
| "requestanimationframe", | ||
| "rAF", | ||
| "fps", | ||
@@ -20,2 +21,3 @@ "fps-limit", | ||
| "delta-time", | ||
| "time-scale", | ||
| "tick", | ||
@@ -22,0 +24,0 @@ "render-loop", |
+27
-15
@@ -9,3 +9,3 @@ # Ticker | ||
| A lightweight, zero-dependency JavaScript/TypeScript library for running an application loop with **separate logic and | ||
| draw ticks**, **FPS limiting**, and **low-FPS detection**. | ||
| draw ticks**, **time scaling**, **FPS limiting** and **low-FPS detection**. | ||
| <hr> | ||
@@ -17,2 +17,3 @@ | ||
| - ποΈ **FPS control** β cap your draw rate to save CPU/battery | ||
| - β±οΈ **Time scaling** β slow down or speed up time without touching your game logic | ||
| - π **Low-FPS callbacks** β react when performance degrades (e.g. lower visual quality) | ||
@@ -50,14 +51,16 @@ - π§ͺ **Test-friendly** β ships with `TickerMock` for deterministic unit tests | ||
| - start() - starts ticker. | ||
| - stop() - stops ticker. | ||
| - isStarted() - checks if ticker is started. | ||
| - start() β starts ticker. | ||
| - stop() β stops ticker. | ||
| - isStarted() β checks if ticker is started. | ||
| - setFps(options: { min?: number; max?: number; expected?: number }) - set min, max or expected FPS | ||
| - min (default: 0) - defines value at which lowFps task callbacks will be called. | ||
| - max (default: 60) - defines drawing FPS limit. | ||
| - min (default: 0) β defines value at which lowFps task callbacks will be called. | ||
| - max (default: 60) β defines drawing FPS limit. | ||
| - expected (default: 60) - defines expected logical and drawing FPS at which app should work in normal conditions. | ||
| - addLogicTask(callback) - register callback for update app logic. Returns TickerTaskId. | ||
| - addDrawTask(callback) - register callback for update app screen. Returns TickerTaskId. | ||
| - addLowFpsTask(callback) - register callback that will be called when reached min FPS. Returns TickerTaskId. | ||
| - remove(taskId: TickerTaskId) - removes provided task. | ||
| - fps() - current FPS at which app operate. | ||
| - setTimeScale(scale: number) β set time scale. | ||
| - addLogicTask(callback) β register callback for update app logic. Returns TickerTaskId. | ||
| - addDrawTask(callback) β register callback for update app screen. Returns TickerTaskId. | ||
| - addLowFpsTask(callback) β register callback that will be called when reached min FPS. Returns TickerTaskId. | ||
| - remove(taskId: TickerTaskId) β removes the provided task. | ||
| - fps() β current FPS at which app operates. | ||
| - timeScale() β current time scale at which app operates. | ||
@@ -71,5 +74,5 @@ ### Migration | ||
| ```typescript | ||
| import {Ticker} from '@armniko/ticker'; | ||
| import { Ticker } from '@armniko/ticker'; | ||
| const element: { position: { x: number, y: number } } = {position: {x: 0, y: 0}}; | ||
| const element: { position: { x: number, y: number } } = { position: { x: 0, y: 0 } }; | ||
| const animation: { durationMs: number, distancePx: number } = { | ||
@@ -94,5 +97,5 @@ durationMs: 2000, | ||
| ```typescript | ||
| import {Ticker, Time} from '@armniko/ticker'; | ||
| import { Ticker, Time } from '@armniko/ticker'; | ||
| const element: { position: { x: number, y: number } } = {position: {x: 0, y: 0}}; | ||
| const element: { position: { x: number, y: number } } = { position: { x: 0, y: 0 } }; | ||
| const animation: { durationMs: number, distancePx: number } = { | ||
@@ -117,2 +120,11 @@ durationMs: 2000, | ||
| <tr> | ||
| <td>v2.3.0</td> | ||
| <td> | ||
| Added time scale feature.<br> | ||
| Minor performance improvements.<br> | ||
| Fixed low-fps callback to be called gain after fps recovery.<br> | ||
| Fixed edge case bug with a removing task where an incorrect task could be removed.<br> | ||
| </td> | ||
| </tr> | ||
| <tr> | ||
| <td>v2.2.0</td> | ||
@@ -119,0 +131,0 @@ <td> |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
25996
16.64%660
18.49%165
7.84%0
-100%