// vim: set ts=4 sw=4 tw=99 noet:
//
// HLDS Cron
// Copyright (C) hlds.run contributors.
//
// Cron scheduler API for AMX Mod X plugins.
//
// This include provides the public API for a cron scheduler. Include it in
// any plugin that needs wall-clock scheduling:
//
// #include <cron>
//
// The scheduler uses the familiar five-field cron notation:
//
// ┌──────── minute 0-59
// │ ┌────── hour 0-23
// │ │ ┌──── day of month 1-31
// │ │ │ ┌── month 1-12
// │ │ │ │ ┌ day of week 0-6 (Sunday = 0)
// │ │ │ │ │
// * * * * *
//
// Supported expressions:
// * any value
// */n every n units
// a exact value
// a-b inclusive range
// a,b,c list
// a-b/n range with step
//
// Examples:
// */5 * * * * Every five minutes
// 0 * * * * Every hour
// 0 4 * * * Every day at 04:00
// 30 3 * * 1-5 Weekdays at 03:30
// 0 0 1 * * First day of every month
//
// Cron jobs use server local wall-clock time. Cron is intended for calendar
// schedules; use cron_every() for elapsed-time intervals.
//
// The API is deliberately small and follows the style of AMX Mod X include
// files: constants first, then public declarations, then detailed native
// documentation and examples.
//
// See the AMX Mod X module/include documentation for the native model and
// include conventions.
#if defined _cron_included
#endinput
#endif
#define _cron_included
/**
* No special scheduler flags.
*/
#define CRON_FLAG_NONE 0
/**
* Run the job once immediately after registration, then continue normally.
*/
#define CRON_FLAG_RUN_ON_START (1 << 0)
/**
* Do not execute occurrences that were missed while the scheduler was not
* running. This is the recommended default.
*/
#define CRON_FLAG_SKIP_MISSED (1 << 1)
/**
* Replay missed occurrences after the scheduler resumes.
*
* Implementations should protect against an excessive number of executions
* with an internal replay limit.
*/
#define CRON_FLAG_REPLAY_MISSED (1 << 2)
/**
* Execute at the next matching cron occurrence and remove the job.
*/
#define CRON_FLAG_ONCE (1 << 3)
/**
* Result returned by cron_validate() and lifecycle functions.
*/
enum CronResult
{
CRON_OK = 0,
CRON_ERR_INVALID_EXPRESSION,
CRON_ERR_INVALID_CALLBACK,
CRON_ERR_INVALID_FLAGS,
CRON_ERR_LIMIT_REACHED,
CRON_ERR_NOT_FOUND,
CRON_ERR_ALREADY_EXISTS,
CRON_ERR_INTERNAL
};
/**
* Current scheduler state.
*/
enum CronSchedulerState
{
CRON_STATE_STOPPED = 0,
CRON_STATE_RUNNING
};
/**
* Creates a recurring cron job.
*
* The callback must be a public Pawn function with the following signature:
*
* public my_callback(job_id)
*
* @param expression
* Five-field cron expression.
*
* @param callback
* Name of the public callback function.
*
* @param flags
* Combination of CRON_FLAG_* values.
*
* @return
* Positive job ID on success, 0 on failure.
*
* @note The callback is executed in the normal AMX Mod X execution context.
* It must not perform long-running blocking work.
*
* @example
* public plugin_init()
* {
* cron_schedule("*/5 * * * *", "cron_five_minutes");
* }
*
* public cron_five_minutes(job_id)
* {
* server_print("[CRON] job #%d", job_id);
* }
*/
native cron_schedule(const expression[], const callback[], flags = CRON_FLAG_SKIP_MISSED);
/**
* Creates a recurring cron job with a stable human-readable name.
*
* Named jobs are useful for administration, diagnostics and idempotent
* registration.
*
* @param name
* Unique job name, for example "daily_cleanup".
*
* @param expression
* Five-field cron expression.
*
* @param callback
* Name of the public callback function.
*
* @param flags
* Combination of CRON_FLAG_* values.
*
* @return
* Positive job ID on success, 0 on failure.
*
* @note Job names are unique within the scheduler.
*
* @example
* public plugin_init()
* {
* cron_schedule_named(
* "daily_cleanup",
* "0 3 * * *",
* "cron_daily_cleanup"
* );
* }
*/
native cron_schedule_named(
const name[],
const expression[],
const callback[],
flags = CRON_FLAG_SKIP_MISSED
);
/**
* Cancels a job.
*
* @param job_id
* Job ID returned by cron_schedule() or cron_schedule_named().
*
* @return
* true if the job existed and was cancelled.
*/
native bool:cron_cancel(job_id);
/**
* Cancels a named job.
*
* @param name
* Name supplied to cron_schedule_named().
*
* @return
* true if the job existed and was cancelled.
*/
native bool:cron_cancel_named(const name[]);
/**
* Checks whether a job exists.
*
* @param job_id
* Job ID.
*
* @return
* true if the job is active.
*/
native bool:cron_exists(job_id);
/**
* Checks whether a named job exists.
*
* @param name
* Job name.
*
* @return
* true if the job is active.
*/
native bool:cron_exists_named(const name[]);
/**
* Cancels all jobs owned by the calling plugin.
*
* @note The scheduler automatically removes jobs when their owning plugin
* is unloaded.
*/
native cron_cancel_all();
/**
* Returns the number of active jobs owned by the calling plugin.
*
* @return
* Number of active jobs.
*/
native cron_count();
/**
* Returns the total number of active jobs in the scheduler.
*
* @return
* Number of active jobs.
*/
native cron_total();
/**
* Starts the scheduler.
*
* Normally the scheduler starts automatically. This function exists for
* explicit lifecycle control and testing.
*
* @return
* CRON_OK on success.
*/
native CronResult:cron_start();
/**
* Stops the scheduler without removing registered jobs.
*
* Starting it again resumes scheduling.
*
* @return
* CRON_OK on success.
*/
native CronResult:cron_stop();
/**
* Returns the current scheduler state.
*
* @return
* CRON_STATE_STOPPED or CRON_STATE_RUNNING.
*/
native CronSchedulerState:cron_state();
/**
* Validates a cron expression.
*
* @param expression
* Five-field cron expression.
*
* @param error
* Buffer receiving a human-readable error message.
*
* @param error_len
* Size of the error buffer.
*
* @return
* CRON_OK when valid, otherwise an appropriate CRON_ERR_* value.
*
* @example
* new error[128];
*
* if (cron_validate("0 25 * * *", error, charsmax(error)) != CRON_OK)
* {
* server_print("[CRON] %s", error);
* }
*/
native CronResult:cron_validate(const expression[], error[], error_len);
/**
* Calculates the next matching timestamp for an expression.
*
* @param expression
* Five-field cron expression.
*
* @param from_timestamp
* Unix timestamp from which the search starts. If 0, current server time
* is used.
*
* @return
* Unix timestamp of the next occurrence, or 0 when no occurrence exists.
*
* @example
* new timestamp = cron_next("0 4 * * *");
*
* if (timestamp)
* {
* new date[32];
* format_time(date, charsmax(date), "%Y-%m-%d %H:%M:%S", timestamp);
* server_print("[CRON] next run: %s", date);
* }
*/
native cron_next(const expression[], from_timestamp = 0);
/**
* Returns the timestamp of the last execution of a job.
*
* @param job_id
* Job ID.
*
* @return
* Unix timestamp, or 0 if the job has never executed or does not exist.
*/
native cron_last_run(job_id);
/**
* Returns the timestamp of the next execution of a job.
*
* @param job_id
* Job ID.
*
* @return
* Unix timestamp, or 0 if the job does not exist.
*/
native cron_next_run(job_id);
/**
* Gets the name of a job.
*
* @param job_id
* Job ID.
*
* @param buffer
* Destination buffer.
*
* @param buffer_len
* Destination buffer size.
*
* @return
* true when the job exists.
*/
native bool:cron_get_name(job_id, buffer[], buffer_len);
/**
* Gets the cron expression of a job.
*
* @param job_id
* Job ID.
*
* @param buffer
* Destination buffer.
*
* @param buffer_len
* Destination buffer size.
*
* @return
* true when the job exists.
*/
native bool:cron_get_expression(job_id, buffer[], buffer_len);
/**
* Gets the callback name of a job.
*
* @param job_id
* Job ID.
*
* @param buffer
* Destination buffer.
*
* @param buffer_len
* Destination buffer size.
*
* @return
* true when the job exists.
*/
native bool:cron_get_callback(job_id, buffer[], buffer_len);
/**
* Immediately dispatches a job.
*
* Manual execution does not modify the cron schedule or next occurrence.
*
* @param job_id
* Job ID.
*
* @return
* true when the job was found and dispatched.
*
* @note This is intended for administration, testing and diagnostics.
*/
native bool:cron_trigger(job_id);
/**
* Creates a one-shot elapsed-time job.
*
* @param delay
* Delay in seconds.
*
* @param callback
* Public callback name.
*
* @return
* Positive job ID on success, 0 on failure.
*
* @example
* cron_once(30.0, "cron_after_thirty_seconds");
*
* public cron_after_thirty_seconds(job_id)
* {
* server_print("[CRON] delayed task");
* }
*/
native cron_once(Float:delay, const callback[]);
/**
* Creates a recurring elapsed-time job.
*
* Unlike cron_schedule(), this function is based on elapsed time rather than
* calendar time.
*
* @param interval
* Interval in seconds.
*
* @param callback
* Public callback name.
*
* @return
* Positive job ID on success, 0 on failure.
*
* @example
* cron_every(60.0, "cron_every_minute");
*
* public cron_every_minute(job_id)
* {
* server_print("[CRON] interval task");
* }
*/
native cron_every(Float:interval, const callback[]);
/**
* Sets the scheduler log level.
*
* Suggested levels:
*
* 0 - errors only
* 1 - normal
* 2 - verbose/debug
*
* @param level
* Desired log level.
*/
native cron_set_log_level(level);
/* -------------------------------------------------------------------------- */
/* Callback contract */
/* -------------------------------------------------------------------------- */
/**
* Cron callbacks receive exactly one argument: the job ID.
*
* Example:
*
* public cron_daily_cleanup(job_id)
* {
* // Do a short, non-blocking operation.
* }
*
* The scheduler invokes callbacks synchronously. A callback should therefore
* return quickly and should never wait for external I/O.
*/
/* -------------------------------------------------------------------------- */
/* Cron expression reference */
/* -------------------------------------------------------------------------- */
/**
* Minute
*
* 0-59
*
* Hour
*
* 0-23
*
* Day of month
*
* 1-31
*
* Month
*
* 1-12
*
* Day of week
*
* 0-6
* Sunday = 0
*
* Operators:
*
* * Any value
* */5 Every five units
* 1 Exact value
* 1-5 Inclusive range
* 1,3,5 List
* 1-10/2 Range with step
*
* Examples:
*
* * * * * *
* Every minute.
*
* */5 * * * *
* Every five minutes.
*
* 0 * * * *
* At minute zero of every hour.
*
* 30 4 * * 1-5
* 04:30 Monday through Friday.
*
* 0 0 1 * *
* 00:00 on the first day of every month.
*
* 0 0 1 */3 *
* 00:00 on the first day of every third month.
*
* When both day-of-month and day-of-week are restricted, matching follows
* traditional cron semantics: the two fields are combined using OR.
*/
/* -------------------------------------------------------------------------- */
/* Scheduler behaviour */
/* -------------------------------------------------------------------------- */
/**
* Cron jobs are calendar-based and have minute-level precision.
*
* The implementation should:
*
* 1. Evaluate the current wall-clock minute.
* 2. Ensure an occurrence is dispatched at most once.
* 3. Avoid reparsing expressions during normal scheduler ticks.
* 4. Detect backward clock jumps and avoid duplicate execution.
* 5. Handle forward clock jumps without replaying an unbounded number of
* occurrences.
*
* The scheduler should normalize an expression when a job is registered.
* Matching should operate on pre-parsed fields/bitmasks.
*
* A practical implementation may wake the scheduler every 0.5-1.0 seconds,
* while cron matching itself remains minute-based.
*
* Jobs belong to the plugin that registered them. When a plugin is unloaded,
* its jobs must be removed automatically.
*
* Map changes must not cause a job to execute twice for the same wall-clock
* minute.
*/
/* -------------------------------------------------------------------------- */
/* Design notes for implementations */
/* -------------------------------------------------------------------------- */
/**
* Implementation notes:
*
* - Parse expressions only when they are created or changed.
* - Represent each cron field as a compact bitmask where practical.
* - Keep one scheduler tick for all jobs instead of one engine timer per job.
* - Track the last processed calendar minute per job.
* - Validate callback names during registration.
* - Associate every job with its owner plugin.
* - Do not execute Pawn callbacks from a worker thread.
* - Keep default behaviour conservative: missed executions are skipped.
* - Provide useful diagnostics for malformed expressions and callback errors.
*
* The public API is intentionally written as if cron were a normal AMX Mod X
* library available to plugins. The implementation can be backed by an AMXX
* module or by a Pawn library without requiring plugin authors to change the
* API.
*/