Overview

OwlScheduler is a task scheduling utility designed to manage, defer and budget execution time across frames. Instead of cluttering your code with task.wait(), task.spawn() or unbounded RunService connections, OwlScheduler lets you schedule tasks with priority queues, configurable execution budgets and automatic error isolation.

Key Features

  • Budget Control: Limit task execution time per frame (in milliseconds) to prevent frame drops.
  • Multiple Queues: Separate queues for interval tasks, frame-by-frame updates, client rendering and deferred actions.
  • Safe Execution: Every scheduled task runs inside a pcall wrapper, preventing one failing task from breaking the whole loop.
  • Flexible Handles: Cancel, inspect or clean up scheduled tasks dynamically using standard TaskHandle objects.

Basic Usage

Scheduling a One-Time or Recurring Task

Use :Add() to schedule a simple task or :Every() to run a function at fixed intervals:

ReplicatedStorage/Controllers/GameController.lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Owl = require(ReplicatedStorage.OwlKnit.Owl)
local OwlScheduler = Owl.Util.Scheduler
 
-- > // Run a task every 2 seconds
local handle = OwlScheduler:Every(2, function(dt)
    print("Periodic check triggered after", dt, "seconds")
end, {
    Priority = 1,
    BudgetCost = 0.1,
})
 
-- > // Stop the task whenever needed
task.wait(10)
handle:Cancel()

Running Logic Until a Condition Ends

Use :Until() when a callback should execute repeatedly until a condition evaluates to true:

OwlScheduler:Until(function(dt)
    -- > // Returning true automatically cancels and unqueues the task
    if characterIsDead() then
        return true
    end
    
    updateHealthBar(dt)
    return false
end)

Task Lifecycle & Handles

Whenever you add a task, OwlScheduler returns a TaskHandle. You can use this handle to check its running state or cancel it early without keeping track of connection objects:

local handle = OwlScheduler:Add(function()
    print("Executing task...")
end, { Delay = 5 })
 
if handle:IsRunning() then
    handle:Cancel() -- > // Task will be cleaned up on the next cycle
end

Queues & Budgeting

To prevent execution spikes from causing lag, OwlScheduler categorizes tasks into four distinct queues and enforces a strict millisecond budget during frame updates.

Available Queues

Each queue targets a specific phase of the Roblox lifecycle:

Queue Method Targeted Hook Description
:Add() / :Every() Heartbeat General task queue subject to the millisecond budget limit.
:NextFrame() Heartbeat High-priority queue executed once at the start of the next frame (unbudgeted).
:OnRender() RenderStepped Client-only queue executed right before rendering each frame.
:Defer() Heartbeat Low-priority queue drained after main tasks, subject to budget limits.

Budgeting Mechanics

By default, OwlScheduler allocates 2 ms per frame (DefaultBudgetMS) for standard and deferred queues.

During each Heartbeat, OwlScheduler drains the frame queue first, then checks whether remaining time fits the task's estimated BudgetCost before executing it:

-- > // Adjust the global execution budget to 4 ms
OwlScheduler:SetBudget(4)
 
OwlScheduler:Every(0.5, function()
    -- > // Heavy task execution
end, {
    BudgetCost = 0.5, -- > // Estimated millisecond cost
})

If a task's BudgetCost exceeds the remaining frame budget, execution for that queue pauses until the next Heartbeat frame.

Queue Priority

Within each queue, tasks are processed according to their Priority property (lower numbers execute first):

OwlScheduler:Add(function()
    print("Executes second")
end, { Priority = 10 })
 
OwlScheduler:Add(function()
    print("Executes first")
end, { Priority = 1 })

Monitoring Performance

You can query performance statistics at runtime using :GetStats() to monitor frame times and queue sizes:

local stats = OwlScheduler:GetStats()
 
print(("Last Frame: %.2f ms | Avg Frame: %.2f ms"):format(stats.LastFrameMs, stats.AverageFrameMs))
print(("Task Queue Length: %d"):format(stats.TaskQueueLength))

API Reference

This section details all types, options, and methods available in OwlScheduler.

Type Definitions

TaskFn

type TaskFn = (dt: number) -> boolean?

A callback function that receives the delta time (dt). Returning true will automatically cancel the task.

TaskOptions

type TaskOptions = {
    Priority: number?, -- > // Lower numbers run first (default: 0)
    Delay: number?, -- > // Delay in seconds before first execution
    Interval: number?, -- > // Interval in seconds between executions
    Repeat: number?, -- > // Maximum number of execution cycles
    BudgetCost: number?, -- > // Estimated time cost in ms (default: 0.05)
}

TaskHandle

type TaskHandle = {
    Cancel: (self: TaskHandle) -> (),
    Destroy: (self: TaskHandle) -> (),
    IsRunning: (self: TaskHandle) -> boolean,
}

SchedulerStats

type SchedulerStats = {
    BudgetMs: number,
    LastFrameMs: number,
    AverageFrameMs: number,
    TaskQueueLength: number,
    FrameQueueLength: number,
    RenderQueueLength: number,
    DeferredQueueLength: number,
}

Methods

:Add(fn, opts)

Enqueues a task into the main task queue.

  • Parameters:
    • fn: TaskFn
    • opts: TaskOptions?
  • Returns: TaskHandle

:Every(interval, fn, opts)

Schedules a task to run repeatedly at a fixed interval.

  • Parameters:
    • interval: number (must be non-negative)
    • fn: TaskFn
    • opts: TaskOptions?
  • Returns: TaskHandle

:NextFrame(fn, opts)

Schedules a task to run once on the very next Heartbeat frame.

  • Parameters:
    • fn: TaskFn
    • opts: TaskOptions?
  • Returns: TaskHandle

:OnRender(fn, opts)

Schedules a task to run during RenderStepped. Client only.

  • Parameters:
    • fn: TaskFn
    • opts: TaskOptions?
  • Returns: TaskHandle

:Defer(fn, opts)

Schedules a task in the deferred queue to be processed after main tasks.

  • Parameters:
    • fn: TaskFn
    • opts: TaskOptions?
  • Returns: TaskHandle

:Until(fn, opts)

Executes a function every frame until it returns true.

  • Parameters:
    • fn: (dt: number) -> boolean
    • opts: TaskOptions?
  • Returns: TaskHandle

:SetBudget(ms) / :GetBudget()

Gets or sets the execution budget per frame in milliseconds.

  • Parameters: ms: number (> 0)

:GetStats()

Returns a SchedulerStats object with active queue lengths and performance measurements.

  • Returns: SchedulerStats