CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-scheduler

Cooperative scheduler for the browser environment that provides time-slicing capabilities for JavaScript applications.

Pending
Overview
Eval results
Files

timing-yielding.mddocs/

Timing and Yielding

Utilities for controlling when the scheduler yields control to the browser and managing frame rates for optimal performance.

Capabilities

Should Yield

Determines whether the scheduler should yield control back to the browser's main thread.

/**
 * Check if the scheduler should yield control to the browser
 * @returns true if the scheduler should yield, false otherwise
 */
function unstable_shouldYield(): boolean;

This function helps implement cooperative scheduling by indicating when the current time slice has been exhausted. Tasks should check this periodically during long-running operations.

Usage Examples:

import { unstable_shouldYield } from "scheduler";

// Process large dataset with yielding
function processLargeArray(items) {
  const results = [];
  
  for (let i = 0; i < items.length; i++) {
    // Process current item
    results.push(processItem(items[i]));
    
    // Yield if we've used up our time slice
    if (unstable_shouldYield()) {
      // Return continuation to finish remaining work
      return () => {
        return processLargeArray(items.slice(i + 1));
      };
    }
  }
  
  return results; // All work completed
}

// Use within scheduled callback
import { unstable_scheduleCallback, unstable_NormalPriority } from "scheduler";

unstable_scheduleCallback(unstable_NormalPriority, function processWork(didTimeout) {
  let workCompleted = 0;
  
  while (workQueue.length > 0 && !unstable_shouldYield()) {
    const workItem = workQueue.shift();
    performWork(workItem);
    workCompleted++;
  }
  
  console.log(`Completed ${workCompleted} items this slice`);
  
  // Continue if more work remains
  if (workQueue.length > 0) {
    return processWork;
  }
});

Now

Returns the current high-resolution timestamp, similar to performance.now().

/**
 * Get current high-resolution timestamp
 * @returns Current time in milliseconds with sub-millisecond precision
 */
function unstable_now(): number;

Usage Examples:

import { unstable_now } from "scheduler";

// Measure execution time
const startTime = unstable_now();
performExpensiveOperation();
const endTime = unstable_now();
console.log(`Operation took ${endTime - startTime}ms`);

// Implement custom timeout logic
function processWithTimeout(maxDuration) {
  const startTime = unstable_now();
  
  while (hasMoreWork()) {
    processWorkItem();
    
    // Check if we've exceeded our time budget
    if (unstable_now() - startTime > maxDuration) {
      console.log("Timeout reached, yielding");
      break;
    }
  }
}

// Track performance metrics
const performanceMetrics = {
  taskStartTime: null,
  
  startTask() {
    this.taskStartTime = unstable_now();
  },
  
  endTask() {
    const duration = unstable_now() - this.taskStartTime;
    console.log(`Task completed in ${duration}ms`);
  }
};

Request Paint

Signals to the scheduler that a paint is needed, which may influence yielding decisions.

/**
 * Request that the browser paint soon
 * May influence the scheduler's yielding behavior
 */
function unstable_requestPaint(): void;

Usage Examples:

import { unstable_requestPaint, unstable_scheduleCallback, unstable_NormalPriority } from "scheduler";

// Request paint after visual updates
function updateMultipleElements() {
  unstable_scheduleCallback(unstable_NormalPriority, () => {
    // Update DOM elements
    updateElement1();
    updateElement2();
    updateElement3();
    
    // Signal that visual changes were made
    unstable_requestPaint();
  });
}

// Use in animation loops
function animationStep() {
  unstable_scheduleCallback(unstable_NormalPriority, function animate(didTimeout) {
    // Update animation state
    updateAnimationFrame();
    
    // Request paint to show changes
    unstable_requestPaint();
    
    // Continue animation if not complete
    if (animationRunning) {
      return animate;
    }
  });
}

// Batch DOM updates and request paint
function batchDOMUpdates(updates) {
  unstable_scheduleCallback(unstable_NormalPriority, () => {
    updates.forEach(update => {
      applyDOMUpdate(update);
    });
    
    // All DOM changes complete, request paint
    unstable_requestPaint();
  });
}

Force Frame Rate

Forces the scheduler to target a specific frame rate by adjusting the yielding interval.

/**
 * Force scheduler to target a specific frame rate
 * @param fps - Target frames per second (0-125), or 0 to reset to default
 */
function unstable_forceFrameRate(fps: number): void;

Usage Examples:

import { unstable_forceFrameRate } from "scheduler";

// Set 30 FPS for performance-constrained environments
unstable_forceFrameRate(30);

// Set 60 FPS for smooth animations
unstable_forceFrameRate(60);

// Reset to default frame rate (browser-dependent, typically 60 FPS)
unstable_forceFrameRate(0);

// Adaptive frame rate based on device capabilities
function setAdaptiveFrameRate() {
  const isLowEndDevice = navigator.hardwareConcurrency <= 2;
  const targetFPS = isLowEndDevice ? 30 : 60;
  
  unstable_forceFrameRate(targetFPS);
  console.log(`Frame rate set to ${targetFPS} FPS`);
}

// Frame rate for specific scenarios
function enterGameMode() {
  unstable_forceFrameRate(60); // Smooth gaming experience
}

function enterPowerSaveMode() {
  unstable_forceFrameRate(15); // Conserve battery
}

function exitSpecialMode() {
  unstable_forceFrameRate(0); // Reset to default
}

Yielding Behavior

The scheduler's yielding behavior is controlled by several factors:

  1. Time Slice Duration: Default 5ms, adjustable via unstable_forceFrameRate
  2. Paint Requests: unstable_requestPaint may cause earlier yielding
  3. Priority Levels: Different priorities have different timeout behaviors
  4. Browser Events: High-priority browser events may trigger yielding

Performance Considerations

Optimal Yielding Patterns

// Good: Check shouldYield in loops
function processItems(items) {
  for (let i = 0; i < items.length; i++) {
    processItem(items[i]);
    
    if (unstable_shouldYield()) {
      // Yield and continue with remaining items
      return () => processItems(items.slice(i + 1));
    }
  }
}

// Good: Use requestPaint after visual updates
function updateVisuals() {
  updateDOM();
  unstable_requestPaint(); // Ensure changes are painted
}

// Avoid: Not checking shouldYield in long operations
function badProcessItems(items) {
  // This blocks the main thread
  for (let i = 0; i < items.length; i++) {
    processItem(items[i]); // No yielding check
  }
}

Frame Rate Guidelines

  • 60 FPS: Smooth animations and interactions
  • 30 FPS: Acceptable for most content, conserves resources
  • 15 FPS: Power saving mode, minimal interactivity
  • 0 (default): Let browser determine optimal frame rate

Integration with Browser APIs

The scheduler integrates with various browser timing APIs:

  • performance.now(): Used internally by unstable_now()
  • MessageChannel: Primary scheduling mechanism in browsers
  • setTimeout: Fallback scheduling mechanism
  • setImmediate: Used in Node.js environments when available

Install with Tessl CLI

npx tessl i tessl/npm-scheduler

docs

core-scheduling.md

index.md

priority-management.md

profiling.md

testing-utilities.md

timing-yielding.md

tile.json