Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ CDN usage:
| Procedural generation | `perlin`, `perlin3D`, `simplex2D`, `simplex3D`, `worley`, `worleySample`, `waveFunctionCollapse`, `cellularAutomataCave`, `poissonDiskSampling`, `computeVoronoiDiagram`, `diamondSquare`, `generateLSystem`, `generateBspDungeon`, `generateRecursiveMaze`, `generatePrimMaze`, `generateKruskalMaze`, `generateWilsonMaze`, `generateAldousBroderMaze`, `generateRecursiveDivisionMaze` | `procedural/*.ts` | `examples/simplex.ts`, `examples/worley.ts`, `examples/waveFunctionCollapse.ts`, `examples/cellularAutomata.ts`, `examples/poissonDisk.ts`, `examples/voronoi.ts`, `examples/diamondSquare.ts`, `examples/lSystem.ts`, `examples/dungeonBsp.ts`, `examples/mazeRecursive.ts`, `examples/mazePrim.ts`, `examples/mazeKruskal.ts`, `examples/mazeWilson.ts`, `examples/mazeAldous.ts`, `examples/mazeDivision.ts` |
| Spatial queries & collision | `Quadtree`, `aabbCollision`, `aabbIntersection`, `satCollision`, `circleRayIntersection`, `sweptAABB` | `spatial/*.ts` | `examples/sat.ts` |
| AI behaviours & crowds | `seek`, `flee`, `arrive`, `pursue`, `wander`, `updateBoids`, `BehaviorTree`, `rvoStep` | `ai/steering.ts`, `ai/boids.ts`, `ai/behaviorTree.ts`, `ai/rvo.ts` | `examples/steering.ts`, `examples/boids.ts`, `examples/rvo.ts` |
| Web performance & UI | `debounce`, `throttle`, `LRUCache`, `memoize`, `deduplicateRequest`, `clearRequestDedup`, `calculateVirtualRange`, `createWeightedAliasSampler`, `createObjectPool`, `fisherYatesShuffle`, `createFixedTimestepLoop` | `util/*.ts` | `examples/requestDedup.ts`, `examples/virtualScroll.ts`, `examples/weightedAlias.ts`, `examples/objectPool.ts`, `examples/fisherYates.ts`, `examples/fixedTimestep.ts` |
| Web performance & UI | `debounce`, `throttle`, `LRUCache`, `memoize`, `deduplicateRequest`, `clearRequestDedup`, `calculateVirtualRange`, `createWeightedAliasSampler`, `createObjectPool`, `createDeltaTimeManager`, `createFixedTimestepLoop`, `fisherYatesShuffle` | `util/*.ts` | `examples/requestDedup.ts`, `examples/virtualScroll.ts`, `examples/weightedAlias.ts`, `examples/objectPool.ts`, `examples/deltaTime.ts`, `examples/fixedTimestep.ts`, `examples/fisherYates.ts` |
| Search & text | `fuzzySearch`, `fuzzyScore`, `Trie`, `binarySearch`, `levenshteinDistance` | `search/*.ts` | `examples/search.ts` |
| Data & diff pipelines | `diff`, `deepClone`, `groupBy`, `diffJson`, `applyJsonDiff` | `data/*.ts` | `examples/jsonDiff.ts` |
| Graph algorithms | `graphBFS`, `graphDFS`, `topologicalSort` | `graph/traversal.ts` | `examples/graph.ts` |
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
- [ ] Maze algorithms pack (Recursive backtracking ✅, Prim's ✅, Kruskal's ✅, Wilson's ✅, Aldous–Broder ✅, Recursive Division ✅)
- Gameplay systems & utilities:
- [x] Fixed-timestep game loop utility with interpolation helpers
- [ ] Delta-time manager for frame-independent timing
- [x] Delta-time manager for frame-independent timing
- [x] Object pool helper for reusable entities
- [x] Weighted random selector (alias method)
- [x] Fisher–Yates shuffle implementation
Expand Down
29 changes: 29 additions & 0 deletions docs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,35 @@ export interface ObjectPool<T> {
*/
export function createObjectPool<T>(options: ObjectPoolOptions<T>): ObjectPool<T>;

/**
* Delta-time manager configuration.
* Use for: clamping frame spikes, tuning smoothing windows.
* Import: util/deltaTime.ts
*/
export interface DeltaTimeOptions {
maxDelta?: number;
smoothing?: number;
}

/**
* Delta-time manager API.
* Use for: sampling frame durations and resetting loops.
* Import: util/deltaTime.ts
*/
export interface DeltaTimeManager {
update(timestamp: number): number;
getDelta(): number;
reset(): void;
}

/**
* Creates a delta-time manager that smooths and clamps frame durations.
* Use for: game loops, animation systems, interpolation factors.
* Performance: O(1) per update with small smoothing window maintenance.
* Import: util/deltaTime.ts
*/
export function createDeltaTimeManager(options?: DeltaTimeOptions): DeltaTimeManager;

/**
* Shuffles an array in place using Fisher–Yates.
* Use for: unbiased permutations, testing, random ordering.
Expand Down
7 changes: 7 additions & 0 deletions examples/deltaTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createDeltaTimeManager } from '../src/index.js';

const manager = createDeltaTimeManager({ smoothing: 2, maxDelta: 0.05 });

console.log('Delta 0:', manager.update(0));
console.log('Delta 1:', manager.update(16));
console.log('Delta 2:', manager.update(32));
13 changes: 13 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const examples = {
calculateVirtualRange: 'examples/virtualScroll.ts',
createWeightedAliasSampler: 'examples/weightedAlias.ts',
createObjectPool: 'examples/objectPool.ts',
createDeltaTimeManager: 'examples/deltaTime.ts',
fisherYatesShuffle: 'examples/fisherYates.ts',
createFixedTimestepLoop: 'examples/fixedTimestep.ts',
},
Expand Down Expand Up @@ -425,6 +426,13 @@ export { createWeightedAliasSampler } from './util/weightedAlias.js';
*/
export { createObjectPool } from './util/objectPool.js';

/**
* Delta-time manager that clamps spikes and smooths frame durations.
*
* Example file: examples/deltaTime.ts
*/
export { createDeltaTimeManager } from './util/deltaTime.js';

/**
* Fisher–Yates shuffling utility for unbiased permutations.
*
Expand All @@ -448,6 +456,11 @@ export type {
VirtualScrollOptions,
} from './util/virtualScroll.js';

/**
* Delta-time manager types for smoothing configuration and runtime control.
*/
export type { DeltaTimeOptions, DeltaTimeManager } from './util/deltaTime.js';

// ============================================================================
// 🔍 SEARCH & STRING UTILITIES
// ============================================================================
Expand Down
96 changes: 96 additions & 0 deletions src/util/deltaTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Configuration options for {@link createDeltaTimeManager}.
* Useful for: bounding large frame spikes and smoothing jitter.
*/
export interface DeltaTimeOptions {
/** Maximum delta in seconds, defaults to 1/10 (100 ms). */
maxDelta?: number;
/** Number of samples to smooth, defaults to 1 (no smoothing). */
smoothing?: number;
}

/**
* Runtime interface returned by {@link createDeltaTimeManager}.
* Useful for: polling delta inside fixed or variable timestep loops.
*/
export interface DeltaTimeManager {
/** Updates the manager with the latest timestamp (ms) and returns smoothed delta (seconds). */
update(timestamp: number): number;
/** Returns the most recent smoothed delta (seconds). */
getDelta(): number;
/** Resets internal state and clears accumulated samples. */
reset(): void;
}

/**
* Creates a delta-time manager for animation/gameplay loops.
* Useful for: smoothing frame time, clamping spikes, and driving interpolation factors.
*
* @example
* ```ts
* const manager = createDeltaTimeManager({ maxDelta: 0.05, smoothing: 4 });
* const delta = manager.update(performance.now());
* ```
*/
export function createDeltaTimeManager({
maxDelta = 0.1,
smoothing = 1,
}: DeltaTimeOptions = {}): DeltaTimeManager {
if (typeof maxDelta !== 'number' || Number.isNaN(maxDelta) || !Number.isFinite(maxDelta)) {
throw new Error('maxDelta must be a finite number.');
}
if (maxDelta <= 0) {
throw new Error('maxDelta must be greater than zero.');
}
if (typeof smoothing !== 'number' || Number.isNaN(smoothing) || smoothing < 1) {
throw new Error('smoothing must be a number >= 1.');
}
if (!Number.isInteger(smoothing)) {
throw new Error('smoothing must be an integer.');
}

let lastTimestamp: number | undefined;
const samples: number[] = [];
let currentDelta = 0;

function update(timestamp: number): number {
if (typeof timestamp !== 'number' || Number.isNaN(timestamp) || !Number.isFinite(timestamp)) {
throw new Error('timestamp must be a finite number.');
}

if (lastTimestamp === undefined) {
lastTimestamp = timestamp;
currentDelta = 0;
return currentDelta;
}

let delta = (timestamp - lastTimestamp) / 1000;
lastTimestamp = timestamp;

if (delta > maxDelta) {
delta = maxDelta;
} else if (delta < 0) {
delta = 0;
}

samples.push(delta);
if (samples.length > smoothing) {
samples.shift();
}

currentDelta = samples.reduce((sum, value) => sum + value, 0) / samples.length;
return currentDelta;
}

function getDelta(): number {
return currentDelta;
}

function reset(): void {
lastTimestamp = undefined;
samples.length = 0;
currentDelta = 0;
}

return { update, getDelta, reset };
}
31 changes: 31 additions & 0 deletions tests/deltaTime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { describe, expect, it } from 'vitest';

import { createDeltaTimeManager } from '../src/index.js';

describe('createDeltaTimeManager', () => {
it('clamps delta by maxDelta and smooths samples', () => {
const manager = createDeltaTimeManager({ maxDelta: 0.05, smoothing: 2 });
expect(manager.update(0)).toBe(0);
expect(manager.update(10)).toBeCloseTo(0.01, 5);
expect(manager.update(200)).toBeCloseTo(0.03, 5); // (0.01 + clamp 0.05) /2
expect(manager.getDelta()).toBeCloseTo(0.03, 5);
});

it('reset clears internal state', () => {
const manager = createDeltaTimeManager();
manager.update(0);
manager.update(16);
manager.reset();
expect(manager.getDelta()).toBe(0);
expect(manager.update(100)).toBe(0);
});

it('validates options and inputs', () => {
expect(() => createDeltaTimeManager({ maxDelta: 0 })).toThrow(/greater than zero/i);
expect(() => createDeltaTimeManager({ smoothing: 0 })).toThrow(/>= 1/i);
expect(() => createDeltaTimeManager({ smoothing: 1.5 })).toThrow(/integer/i);

const manager = createDeltaTimeManager();
expect(() => manager.update(Number.NaN)).toThrow(/finite number/i);
});
});
1 change: 1 addition & 0 deletions tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ describe('package entry point', () => {
| 'calculateVirtualRange'
| 'createWeightedAliasSampler'
| 'createObjectPool'
| 'createDeltaTimeManager'
| 'fisherYatesShuffle'
| 'createFixedTimestepLoop'
>();
Expand Down