Start typing to search packages!
taskscheduler
By @biotoxin495
Roblox
MirroredTaskScheduler โ 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
AddTaskAPI - 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
| Option | Type | Default | Description |
|---|---|---|---|
PollInterval | number | 0.25 | Delay between automatic due-task checks. Must be greater than zero. |
AutoStart | boolean | false | Starts the scheduler during construction. |
OnError | (handle, errorMessage) -> () | nil | Receives callback failures and their tracebacks. |
Schedule options
| Option | Type | Description |
|---|---|---|
Target | any | Organizational key used for grouping and cleanup. Not inspected or owned by the scheduler. |
Name | string | Optional debug name, surfaced through handle:GetName(). |
Metadata | any | Retained 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
Safe for commercial use
Automated license review โ not legal advice.
