dnm-clock
v2.0.10
Published
A high-precision, drift-free 'delay-no-more' clock engine designed to maintain atomic accuracy under extreme main-thread rendering loads.
Maintainers
Readme
DNM Clock
A high-precision, drift-free clock engine designed to maintain atomic accuracy under extreme main-thread rendering loads. Designed for React 18/19.
✨ New Features (v2.0)
- Flexible Formatting: Now supports single-digit tokens for cleaner displays:
h:mm:ss->1:15:09m:ss->5:00s->60
- Countdown Engine: Native support for counting down via
countDirection="DOWN". - Stop-At-Zero: Automatically stops the clock when it reaches
0usingstopAtZero={true}. - Auto-Start: simplified control with
autoStart={true}.
🎯 The Problem
In standard React applications, timers rely on the Main Thread. This is the same thread responsible for:
- Handling User Inputs (Clicks, Typing).
- DOM Diffing and Reconciliation (React Updates).
- CSS Layout and Painting.
- JavaScript execution (Parsing, Logic).
If any of these tasks take too long (e.g., a complex render or a heavy calculation), the Event Loop blocks. setInterval and requestAnimationFrame are delayed. This results in visual stutter, time drift, and "janky" broadcast graphics.
🚀 The Solution: "DNM Clock" Architecture
This Proof of Concept implements a Dual-Thread Architecture:
- The UI Layer (Main Thread): Handles user interactions (Start/Stop/Reset buttons) and sends lightweight commands to the worker.
- The Engine Layer (Worker Thread):
- Timekeeping: Uses
performance.now()for monotonic, high-resolution time measurement independent of system clock skew. - Rendering: Uses
OffscreenCanvasto paint the clock pixels directly from the worker thread. - Isolation: Since the worker runs in parallel, the main thread can be completely frozen (infinite loop), and the clock will continue to tick at 60 FPS.
- Timekeeping: Uses
🏗 System Architecture
1. Zero-Allocation Render Loop (clock.worker.ts)
The worker utilises a highly optimised render loop designed to generate zero garbage collection (GC) pauses during runtime.
- Pre-allocated strings: '00' through '59' are pre-computed.
- Strict Type Checks: No dynamic object creation inside the
requestAnimationFrameloop. - Desynchronised Context: Hints the browser to bypass the compositor for lowest possible latency.
2. The Stress Lab
The application includes a suite of tools designed to break standard React timers:
- CPU Stress Test: Runs a synchronous
while()loop on the main thread, simulating heavy data parsing or crypto operations. - Render Lag Simulator: Spawns 1,500+ un-memoised nodes that re-render every frame to choke the browser's layout engine.
- Chaos Monkey: Randomly spams Start/Stop/Set commands to verify message queue integrity and race-condition handling.
🌏 Browser Support
This library relies on OffscreenCanvas and Web Workers.
| Browser | Version | | :--- | :--- | | Chrome / Edge | 69+ | | Firefox | 105+ | | Safari | 16.4+ |
🔌 Framework Support
- Primary Support: React 18+ (Peer Dependency)
- Engine Core: Framework Agnostic (Worker + OffscreenCanvas). Wrapper required for Vue/Svelte/Angular.
🛠 Usage & Verification
Installation
npm install
npm run devComponent Usage
import { DNMClock } from 'dnm-clock';
<DNMClock
initialSeconds={60}
countDirection="DOWN"
stopAtZero={true}
autoStart={true}
config={{
timeFormat: 'm:ss', // Displays "1:00" -> "0:59"
textColor: '#ff0000',
fontFamily: 'monospace'
}}
/>Running the Comparison
- Start the Timer: Click the green
STARTbutton. Both the "DNM Clock" (Top) and "Naive Clock" (Bottom) will begin counting. - Apply Stress:
- Locate the Stress Testing Lab on the right panel.
- Click "Freeze 2.0s".
- Observe Results:
- ❌ Naive Clock (Main Thread): Will completely stop updating for 2 seconds.
- ✅ DNM Clock (Worker): Will continue ticking smoothly without skipping a single frame.
Running Tests
This project maintains 100% Test Coverage, including the Worker environment and OffscreenCanvas logic.
npm test📂 Project Structure
src/
├── components/
│ ├── DNMClock.tsx # The "Hero" component (Canvas + Worker glue)
│ ├── NaiveClock.tsx # The "Control" group (Standard React State)
│ ├── CpuStressTest.tsx # Main thread blocker
│ └── RenderLagSimulator.tsx # DOM thrashing tool
├── workers/
│ ├── clock.worker.ts # The actual Engine (Canvas painting logic)
│ └── timer.worker.ts # Legacy simple ticker (reference)
└── hooks/
└── useBroadcastMatchTimer.ts # Headless hook implementation🧠 Technical Highlights
- Drift Correction: The timer does not count "ticks". Instead, it calculates
(Now - StartTime) + BaseTimeon every frame. This makes it mathematically impossible for the timer to drift due to skipped frames. - Atomic Adjustments: Adding time (e.g., +1 second) adjusts the
BaseTimeand resets theStartTimeanchor instantly, preserving sub-millisecond precision. - React 19 Compatibility: Uses
useSyncExternalStorepatterns and imperative Ref management to handle the non-React Worker lifecycle safely.
📄 Licence
MIT
