syncopate 0.0.3

A hierarchical, power-aware task scheduler for Rust applications requiring precise timing control
Documentation

Syncopate

A hierarchical, power-aware task scheduler for Rust applications requiring precise timing control.

Overview

Syncopate provides a flexible scheduler for managing periodic tasks with configurable execution windows. It's designed for applications that need:

  • Deterministic timing: Schedule tasks to run at specific intervals
  • Execution windows: Define acceptable time ranges for task execution (early/on-time/late detection)
  • Power efficiency: Idle durations calculated to minimize CPU wakeups
  • Virtual time: Simulated clock for deterministic testing
  • Flexible scheduling: Fixed-rate, fixed-delay, one-shot, and wall-clock-anchored tasks

Quick Start

Add syncopate to your Cargo.toml:

[dependencies]
syncopate = "0.0.1"

Examples

Basic Usage

use std::time::Duration;
use syncopate::{Scheduler, TaskBuilder, Window};

fn main() {
    let mut scheduler = Scheduler::new();

    let task = TaskBuilder::every(Duration::from_secs(1))
        .window(Window::symmetric(Duration::from_millis(50)))
        .name("heartbeat")
        .build()
        .unwrap();

    scheduler.add_task(task).unwrap();

    loop {
        let Some(sleep_dur) = scheduler.calculate_next_tick() else {
            break;
        };
        std::thread::sleep(sleep_dur);

        let result = scheduler.tick();
        for exec in &result.fired {
            println!("{}: drift={}", exec.task.name.as_deref().unwrap_or("?"), exec.drift);
        }
        for miss in &result.missed {
            println!("{}: {} deadlines missed", miss.task.name.as_deref().unwrap_or("?"), miss.deadlines_missed.len());
        }
    }
}

Deterministic Testing with SimClock

use std::rc::Rc;
use std::time::Duration;
use syncopate::{Drift, Scheduler, SimClock, TaskBuilder};

let clock = Rc::new(SimClock::new());
let mut scheduler = Scheduler::new_with_clock(Rc::clone(&clock));

let task = TaskBuilder::every(Duration::from_millis(500))
    .name("test_task")
    .build()
    .unwrap();
scheduler.add_task(task).unwrap();

// Immediate fire at t=0
let result = scheduler.tick();
assert_eq!(result.fired.len(), 1);
assert_eq!(result.fired[0].drift, Drift::OnTime);

// Advance to next deadline
clock.advance(Duration::from_millis(500));
let result = scheduler.tick();
assert_eq!(result.fired.len(), 1);

Wall-Clock-Anchored Tasks

use std::time::Duration;
use syncopate::{Scheduler, TaskBuilder, Window};

let mut scheduler = Scheduler::new();

// Fires on absolute wall-clock boundaries (e.g. every second at :00, :01, :02...)
let task = TaskBuilder::every_absolute(Duration::from_secs(1))
    .window(Window::symmetric(Duration::from_millis(50)))
    .name("wall_clock_tick")
    .build()
    .unwrap();
scheduler.add_task(task).unwrap();

// With offset: fires at 500ms past each second boundary
let task = TaskBuilder::every_absolute(Duration::from_secs(1))
    .offset(Duration::from_millis(500))
    .window(Window::symmetric(Duration::from_millis(50)))
    .name("half_second_tick")
    .build()
    .unwrap();
scheduler.add_task(task).unwrap();

One-Shot Tasks

use std::time::Duration;
use syncopate::{Scheduler, TaskBuilder, Window};

let mut scheduler = Scheduler::new();

// Fire once after 5 seconds
let task = TaskBuilder::once_after(Duration::from_secs(5))
    .window(Window::symmetric(Duration::from_millis(50)))
    .name("delayed_action")
    .build()
    .unwrap();
scheduler.add_task(task).unwrap();

Core Concepts

Task Types

  • Relative (TaskBuilder::every): Period measured from last fire. Supports FixedRate (grid-aligned) and FixedDelay (drift-accumulating) schedules.
  • Absolute (TaskBuilder::every_absolute): Fires on wall-clock boundaries. Handles clock jumps (suspend/resume) gracefully.
  • One-shot (TaskBuilder::once_after, TaskBuilder::once_at): Single-fire tasks that are evicted after execution.

Execution Windows

Tasks define a Window with early and late tolerances:

  • Early: Task fires before the ideal deadline (within tolerance)
  • On-Time: Task fires exactly at the deadline
  • Late: Task fires after the deadline (within tolerance)
  • Missed: Task could not fire within the window

Missed Tick Behavior

When a task misses its window (FixedRate only):

  • Skip (default): Report missed deadlines, advance to current position
  • RunLatest: Fire for the most recent deadline, report earlier ones as missed
  • Burst { max }: Fire multiple missed deadlines in rapid succession

Repeat Control

  • Repeat::Forever (default): Task runs indefinitely
  • Repeat::Times(n): Task fires exactly n times, then is evicted

Timer Delay Compensation

set_timer_delay() compensates for consistent OS timer overhead by waking slightly early.

License

MIT OR Apache-2.0