Ephemeral thread-based task scheduler used to run tasks at a specific time.

// Where multiple types of tasks can be scheduled using the same schedule,
// a tagged union is ideal.
const Task = union(enum) {
	say: []const u8,

	// Whether T is a tagged union (as here) or another type, a public
	// run function must exist
	pub fn run(task: Task, ctx: void, at: i64) void {
		// the original time the task was scheduled for
		_ = at;

		// application-specific context that will be passed to each task
		_ ctx;

		switch (task) {
			.say => |msg| {std.debug.print("{s}\n", .{msg}),
		}
	}
}

...

// This example doesn't use a app-context, so we specify its
// type as void
var s = zul.Scheduler(Task, void).init(allocator);
defer s.deinit();

// Starts the scheduler, launching a new thread
// We pass our context. Since we have a null context
// we pass a null value, i.e. {}
try s.start({});

// will run the say task in 5 seconds
try s.scheduleIn(.{.say = "world"}, std.time.ms_per_s * 5);

// will run the say task in 100 milliseconds
try s.schedule(.{.say = "hello"},  std.time.milliTimestamp() + 100);

zul.Scheduler(T) is a thread-safe way to schedule tasks for future execution. Tasks can be scheduled using the schedule(), scheduleIn() or scheduleAt() methods. These methods are thread-safe.

The scheduler runs in its own thread (started by calling start()). Importantly, tasks are run within the scheduler thread and thus can delay each other. Consider having your tasks use zul.ThreadPool in more advanced cases.

Tasks are not persisted (e.g. if the schedule or program crashes, scheduled jobs are lost). Otherwise, the scheduler never discards a task. Tasks that were scheduled in the past are run normally. Applications that care about stale tasks can use the last parameter passed to T.run which is the original scheduled timestamp (in milliseconds).

T must have a run method. Two variants are supported:

Run when a task is ready to be executed. ctx is the arbitrary data passed to start. at is the timestamp, in millisecond, that the task was supposed to run at.

Same as above, but receives an additional parameter: the scheduler itself. This can be useful if you want to have recurring tasks: run can re-schedule the task.

Stops the task scheduler thread (if it was started) and deallocates the scheduler. This method is thread-safe.

Launches the task scheduler in a new thread. This method is thread-safe. An error is returned if start is called multiple times.

The provided ctx will be passed to T.run. In cases where no ctx is needed, a void context should be used, as shown in the initial example.

Stops the task scheduler. This method is thread-safe. It is safe to call multiple times, even if the scheduler is not started. Since deinit calls this method, it is usually not necessary to call it.

Schedules a task to be run at the specified time. The time is given as a unix timestamp in milliseconds. The time can be in the past.

Schedules a task to be run in the specified milliseconds from now. This is the same as calling schedule with std.time.milliTimestamp() + ms.

Schedules a task to be run at the specified DateTime.