Forest Logo
search
package_2

taskscheduler

By @biotoxin495

Roblox

Mirrored

TaskScheduler โ€” A lightweight scheduler for delayed and timestamped tasks

TaskScheduler is a small, dependency-free Roblox module for running one-shot callbacks at future Unix timestamps or after relative delays.

It gives server and client systems centralized ownership of delayed work: individual cancellation, target-based cleanup, task inspection, and reliable overdue execution โ€” all without a framework, kernel, Signal, Promise, or package dependency.

Quick example

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TaskScheduler = require(ReplicatedStorage.TaskScheduler)

local scheduler = TaskScheduler.new()
scheduler:Start()

scheduler:ScheduleAfter(5, function()
    print("Five seconds have passed")
end)

scheduler:ScheduleAt(os.time() + 60, function()
    print("The requested Unix time has been reached")
end)

Start() is idempotent. Calling it while the scheduler is already running returns false and does not create another loop.

๐Ÿš€ Features

  • Schedule tasks at absolute Unix timestamps with ScheduleAt
  • Schedule tasks after relative delays with ScheduleAfter, including fractional seconds
  • Execute overdue tasks instead of silently skipping them
  • Cancel individual tasks through returned handles
  • Group tasks under any target and cancel the whole group at once
  • Start, stop, manually step, clear, and destroy scheduler instances
  • Prevent duplicate processing loops
  • Run callbacks asynchronously so one task cannot block the scheduler
  • Catch callback errors with tracebacks
  • Optional centralized error handler
  • Optional task names and metadata
  • Compatibility helpers for the original AddTask API
  • Strict Luau types

๐Ÿ“– Basic usage

Copy src/TaskScheduler.lua into your project (or import TaskScheduler.rbxmx) and require it from any script that needs delayed work โ€” it has no client/server restriction.

Creating a scheduler

Pass a configuration table to TaskScheduler.new to set its options.

local scheduler = TaskScheduler.new({
    PollInterval = 0.1,
    AutoStart = true,
    OnError = function(handle, errorMessage)
        warn(handle:GetName(), errorMessage)
    end,
})

A smaller PollInterval improves dispatch precision but wakes the scheduler more frequently.

Scheduling absolute tasks

ScheduleAt uses os.time(). It is suitable for saved timestamps, shared event deadlines, daily resets, offer expiration, and other wall-clock operations.

scheduler:ScheduleAt(expirationTimestamp, expireOffer, {
    Target = offer,
    Name = "ExpireOffer",
    Metadata = { OfferId = offer.Id },
})

If the timestamp has already passed, the task runs during the next scheduler step โ€” it is not discarded.

Scheduling relative tasks

ScheduleAfter uses Roblox's runtime time() clock and supports fractional delays. Delays must be zero or greater.

scheduler:ScheduleAfter(0.5, function()
    print("Half a second later")
end)

Target-based cleanup

Tasks can be grouped by an arbitrary target โ€” a Player, Instance, table, string, or any other non-nil value:

local handle = scheduler:ScheduleAfter(60, callback, {
    Target = player,
})

local cancelledCount = scheduler:CancelTarget(player)

This is useful for player sessions, temporary UI objects, rounds, matches, offers, NPCs, and other objects with a shared lifecycle.

TaskScheduler does not automatically listen for PlayerRemoving or Instance destruction, so connect cleanup explicitly to keep the core module general-purpose:

Players.PlayerRemoving:Connect(function(player)
    scheduler:CancelTarget(player)
end)

โš™๏ธ API

Scheduler

TaskScheduler.new(options?)

Creates a new, isolated scheduler instance.

local scheduler = TaskScheduler.new()

scheduler:Start()

Begins automatic processing. Returns false if already running.

scheduler:Stop()

Stops automatic processing but preserves pending tasks. Overdue tasks are processed normally once the scheduler restarts, and running callbacks are not interrupted.

scheduler:IsRunning()

Returns whether automatic processing is currently active.

scheduler:Step()

Performs one due-task check and dispatches every currently due task. Useful for controlled systems and tests โ€” each callback still runs in its own spawned thread.

local dispatchedCount = scheduler:Step()

scheduler:ScheduleAt(unixTime, callback, options?)

Schedules a callback against Unix time and returns a TaskHandle.

scheduler:ScheduleAfter(delaySeconds, callback, options?)

Schedules a callback after a runtime delay and returns a TaskHandle.

scheduler:Cancel(handleOrId)

Cancels one pending task by handle or numeric ID. Returns true only when the task was still pending and was successfully cancelled.

scheduler:Cancel(handle)
scheduler:Cancel(handle:GetId())

scheduler:CancelTarget(target)

Cancels every pending task associated with target and returns the number cancelled.

scheduler:CancelAt(unixTime, target?)

Cancels absolute tasks (created with ScheduleAt) at an exact timestamp, optionally restricted to one target.

scheduler:GetPendingCount(target?)

Returns the number of pending tasks, or the number pending under a target.

scheduler:Clear()

Cancels every pending task. Running callbacks are unaffected.

scheduler:Destroy()

Stops the processing loop, cancels all pending tasks, and makes the scheduler unusable. Repeated calls are safe.

TaskHandle

Every scheduled task returns a handle:

local handle = scheduler:ScheduleAfter(20, callback)

handle:Cancel()

Cancels the task if it is still pending.

local didCancel = handle:Cancel()

Returns true only on success โ€” a task cannot be cancelled after it begins running.

handle:IsPending()

Returns whether cancellation is still possible.

handle:GetId()

Returns the scheduler-local task ID.

handle:GetStatus()

Returns the current lifecycle status: Pending, Running, Completed, Cancelled, or Failed.

handle:GetScheduledTime()

Returns the Unix or runtime due value.

handle:GetClockKind()

Returns "Unix" or "Runtime", identifying the clock used by the task.

handle:GetTarget()

Returns the organizational target, or nil.

handle:GetName()

Returns the optional debug name, or nil.

handle:GetMetadata()

Returns optional caller-provided metadata, or nil.

Complete configuration reference

Constructor options

OptionTypeDefaultDescription
PollIntervalnumber0.25Delay between automatic due-task checks. Must be greater than zero.
AutoStartbooleanfalseStarts the scheduler during construction.
OnError(handle, errorMessage) -> ()nilReceives callback failures and their tracebacks.

Schedule options

OptionTypeDescription
TargetanyOrganizational key used for grouping and cleanup. Not inspected or owned by the scheduler.
NamestringOptional debug name, surfaced through handle:GetName().
MetadataanyRetained by the handle and not interpreted by the scheduler.

Error handling

Callbacks are executed through xpcall and receive a traceback when they fail.

Without a custom error handler, failures are sent to warn:

local scheduler = TaskScheduler.new()

With centralized handling:

local scheduler = TaskScheduler.new({
    OnError = function(handle, errorMessage)
        warn(("Task %d failed: %s"):format(handle:GetId(), errorMessage))
    end,
})

A failed callback receives the Failed status. One task's failure does not stop the scheduler or other callbacks.

Original API compatibility

The release includes compatibility helpers for the original module:

scheduler:AddTask(target, unixTime, callback)
scheduler:RemoveTask(target, unixTime)
scheduler:RemoveTasksByTarget(target)

They map to:

scheduler:ScheduleAt(unixTime, callback, { Target = target })
scheduler:CancelAt(unixTime, target)
scheduler:CancelTarget(target)

AddTask now returns a task handle. RemoveTask still removes all matching tasks for the target and timestamp, while handle:Cancel() allows individual cancellation.

The old PlayerAdded initialization is no longer necessary because target indexes are created lazily. Player removal should call CancelTarget(player) directly.

๐Ÿ“ Notes

  • Actual dispatch timing depends on PollInterval, Roblox task scheduling, frame time, server load, and callback thread scheduling โ€” TaskScheduler does not guarantee execution on the exact frame or millisecond requested, only that a pending task is dispatched once its scheduled time is reached or passed while the scheduler is processing.
  • Tasks that become overdue while the scheduler is stopped are dispatched after it restarts.
  • Tasks due in the same scheduler step are ordered by scheduled time when they use the same clock, then by creation ID. Callbacks are spawned independently, so callback completion order is not guaranteed.
  • TaskScheduler is an in-memory runtime scheduler. Scheduled callbacks do not survive server shutdown, server crashes, teleports, or a new server session.
  • Lua callbacks cannot be serialized. For persistent behavior, save domain data such as an action type, object ID, and Unix timestamp, then reconstruct the scheduled callback after loading the data in a new server.
  • TaskScheduler is not a distributed or cross-server job system โ€” separate servers maintain separate scheduler instances.
  • The scheduler scans its pending tasks once per processing step. This keeps the implementation small, inspectable, and reliable for ordinary gameplay systems with modest task counts. For very large queues containing thousands of long-lived tasks, a priority queue or dedicated persistent job architecture may be more appropriate.

๐Ÿ› ๏ธ Installation

Roblox Studio

Import TaskScheduler.rbxmx, then place the TaskScheduler ModuleScript somewhere accessible to the scripts that use it, such as ReplicatedStorage or ServerScriptService.

Rojo

Copy src/TaskScheduler.lua into your project, or use the included default.project.json:

rojo serve default.project.json

The included project maps the module to ReplicatedStorage.TaskScheduler, examples to ServerScriptService.TaskSchedulerExamples, and the manual specification to ReplicatedStorage.TaskSchedulerTests.

TaskScheduler/
โ”œโ”€โ”€ default.project.json
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ TaskScheduler.rbxmx
โ”œโ”€โ”€ examples/
โ”‚   โ”œโ”€โ”€ Basic.server.lua
โ”‚   โ””โ”€โ”€ PlayerCleanup.server.lua
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ TaskScheduler.lua
โ””โ”€โ”€ tests/
    โ””โ”€โ”€ TaskScheduler.spec.lua

made with โค๏ธ by biotoxin495

Package Details

Install command (Click to copy)


Version

1.0.0

License

MIT

check_circle

Safe for commercial use

Automated license review โ€” not legal advice.