Skip to content

Decorator Reference ​

Complete reference for all decorators in RedScript.

@load ​

Runs the function when the datapack is loaded or reloaded.

Syntax: @load

rs
@load
fn init() {
    say("Loaded!");
}

Compiles to: Adds function to #minecraft:load function tag.

Use cases:

  • Initialize scoreboards
  • Set up teams
  • Reset game state
  • Display welcome messages

@tick ​

Runs the function every game tick (20 times per second).

Syntax: @tick

rs
@tick
fn update() {
    // runs 20 times per second
}

Compiles to: Adds function to #minecraft:tick function tag.

WARNING

Use @tick sparingly. Running complex logic 20 times per second can cause lag. Prefer @tick(rate=N) for periodic work that does not need every-tick precision.

@tick(rate=N) ​

Registers a periodic callback. N is a positive integer literal up to 2147483647. The first call is after N simulation ticks from load, and reload restarts the phase. Counters are owned by the module. Distinct namespaces and module-qualified functions have independent cadence state. Direct calls to the function remain ordinary calls.

Examples at 20 TPS:

rs
@tick(rate=20)
fn every_second() {
    // runs once per second through the generated periodic dispatcher
}

@tick(rate=1200)
fn every_minute() {
    // runs once per minute
}

Compiles to: a tick-registered dispatcher with a module-owned scoreboard counter. Bare @tick and rate=1 use the direct hook with no counter. The dispatcher itself still checks each tick, so savings depend on the body cost.

@function_tag ​

Registers the function in a Minecraft function tag. This is the generic primitive behind tag-based entry points.

Syntax: @function_tag("namespace:path")

rs
@function_tag("minecraft:load")
fn init() {
    say("Loaded through a function tag");
}

@function_tag("rs:on_player_death")
fn on_player_death() {
    scoreboard_add(@s, "deaths", 1);
}

Compiles to: Adds the function reference to data/<namespace>/tags/function/<path>.json.

@load is equivalent to @function_tag("minecraft:load"), and @tick is equivalent to @function_tag("minecraft:tick"). If both forms target the same tag, the compiler merges and de-duplicates the tag entries.

@on_trigger ​

Runs when a player activates a trigger scoreboard.

Syntax: @on_trigger("trigger_name")

rs
@on_trigger("menu")
fn open_menu() {
    tellraw(@s, "Menu opened!");
}

Player activation: /trigger menu

Compiles to:

  1. Creates a trigger scoreboard objective
  2. Detects when score changes from 0
  3. Resets the trigger after execution

Use cases:

  • Player-activated menus
  • Shop systems
  • Voting systems
  • Custom commands

Legacy specialized event decorators ​

The parser still recognizes the older specialized event decorator names:

  • @on_death
  • @on_login
  • @on_advancement("advancement_id")
  • @on_craft("item_id")
  • @on_join_team("team_name")

These names are kept for compatibility and tooling, but they are not the current runtime-backed event API. Do not use them for new event logic or assume that they generate death/login/advancement/craft/team detection assets.

Use one of these instead:

  • @on(PlayerDeath) / @on(PlayerJoin) / @on(EntityKill) for compiler-known runtime events.
  • @function_tag("namespace:path") plus an explicit Minecraft or stdlib dispatcher for custom event wiring.

@on(EventType) ​

Handles a compiler-known runtime event type. The event runtime injects the executor context, so a zero-argument handler should normally use @s for the triggering player/entity. A legacy single Player parameter is still accepted for compatibility, but it should be treated as an alias for the event executor rather than a normal function argument.

Syntax: @on(EventType)

Supported event types:

EventDescriptionDetection@s context
PlayerDeathA player diesScoreboard/runtime assetPlayer
PlayerJoinA player first appears or reconnectsScoreboard/runtime assetPlayer
EntityKillA player kills an entityScoreboard/runtime assetPlayer

Generic item use is not a compiler-known event. Use @function_tag(...) with an item-specific scoreboard or advancement dispatcher.

rs
@on(PlayerDeath)
fn handle_player_death() {
    scoreboard_add(@s, "deaths", 1);
}

// Legacy-compatible form: the parameter is lowered as the event executor.
@on(PlayerJoin)
fn handle_player_join(player: Player) {
    title(player, "Welcome to the Server!");
}

Compiles to: Adds the handler to the event's function tag (for example rs:on_player_death) and automatically includes the required event runtime asset from the standard library. For unsupported events, use @function_tag plus an explicit stdlib/runtime dispatcher.

@schedule ​

Schedules a function to run once after a fixed delay (in ticks) from the moment the datapack loads or after the decorated function is otherwise triggered.

Syntax: @schedule(ticks=N)

ParameterDescription
ticks=NNumber of ticks to wait before executing the function.
rs
@schedule(ticks=100)
fn delayed_start() {
    // Runs 5 seconds (100 ticks) after datapack load
    say("Game starting!");
}

@schedule(ticks=1200)
fn end_game() {
    // Runs 60 seconds (1200 ticks) after datapack load
    say("Time's up!");
    scoreboard_display("sidebar", "kills");
}

Compiles to: a compiler-generated scheduling wrapper that runs schedule function <ns>:<name> <ticks>t from the startup path.

TIP

For recurring scheduled tasks, use @tick(rate=N). Use @schedule only for one-shot startup delays.

@keep ​

Prevents the dead code elimination (DCE) optimizer from removing a function.

Syntax: @keep

By default, functions whose names start with _ are treated as private and may be removed if unreachable. @keep forces them to be included in the compiled output regardless.

rs
@keep
fn _internal_helper() {
    // Kept even though name starts with _
}

Use cases:

  • Utility functions called only via /function in-game
  • Functions that should survive aggressive DCE

@coroutine ​

Marks a function as a coroutine — a long-running task that can yield control back to Minecraft at loop back-edges, preventing server timeout on expensive computations.

Syntax: @coroutine or @coroutine(batch=N) or @coroutine(onDone="fn_name")

rs
@coroutine(batch=10, onDone="on_done")
fn process_all_players() {
    foreach (p in @a) {
        heavy_computation(p);
        // yields every 10 iterations at the loop back-edge
    }
}

fn on_done() {
    say("Processing complete!");
}

Parameters:

ParameterDescription
batch=NNumber of loop iterations to execute per tick before yielding. Default: 1 (yield every iteration).
onDone="fn"Name of a no-arg function to call when the coroutine finishes all iterations.

Behaviour:

  • Yields at loop back-edges (end of each foreach / for / while body iteration).
  • Uses schedule function ... 1t to resume on the next tick.
  • State is preserved between ticks in a dedicated scoreboard.
  • Only one instance of a coroutine runs at a time; starting a new one while it is running is a no-op.

Use cases:

  • Processing large entity lists without causing /tick warp timeouts
  • Spreading heavy map generation over multiple ticks
  • Chunked bulk operations (inventory scans, world edits)

@inline ​

Hints the compiler to inline this function at call sites.

Syntax: @inline

rs
@inline
fn add(a: int, b: int): int {
    return a + b;
}

Use cases:

  • Performance-critical small functions
  • Avoiding function call overhead

@deprecated ​

Marks a function as deprecated. Compiler emits a warning when called.

Syntax: @deprecated or @deprecated("message")

rs
@deprecated("Use new_api() instead")
fn old_api() { }

@singleton ​

Marks a struct as global scoreboard-backed state and exposes synthetic StructName::get() / StructName::set(value) helpers.

Syntax: @singleton on a struct

rs
@singleton
struct GameState {
    phase: int,
    tick_count: int,
}

@keep
fn update_state() {
    let state = GameState::get()
    state.tick_count = state.tick_count + 1
    GameState::set(state)
}

@watch ​

Runs a parameterless handler when a player's value in a watched scoreboard objective changes.

Syntax: @watch("objective")

rs
@watch("rs.kills")
fn on_score_change() {
    say("Score changed!")
}

@config ​

Injects numeric compile-time configuration values into global let declarations.

Syntax: @config("key", default: N)

rs
@config("max_players", default: 16)
let MAX_PLAYERS: int

@profile ​

Enables performance profiling for this function.

Syntax: @profile

rs
@profile
fn expensive_calculation() {
    // Timing data emitted to game output
}

Retired: @throttle ​

Removed in 4.0. Use @tick(rate=N) for periodic registration and caller-owned cooldown deadlines for call suppression. Ordinary function calls are not rate-limited by tick registration.

@retry ​

Auto-retries the function on failure up to N times.

Syntax: @retry(N)

rs
@retry(3)
fn unstable_operation() {
    // Retries up to 3 times if scoreboard check fails
}

@memoize ​

Caches function results for single-arg int functions (LRU-1).

Syntax: @memoize

rs
@memoize
fn fibonacci(n: int): int {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

@benchmark ​

Enables tick-level benchmarking, outputs timing stats.

Syntax: @benchmark

rs
@benchmark
fn heavy_work() {
    // Timing stats logged to game output
}

@test ​

Marks a function as a test. Run with redscript test.

Syntax: @test

rs
@test
fn test_addition() {
    assert(1 + 1 == 2);
}

Use cases:

  • Unit testing datapack logic
  • CI/CD validation

Decorator Summary ​

DecoratorTrigger@s Context
@loadDatapack load / /reloadServer
@tickEvery tick (20/sec)Server
@tick(rate=N)Every N game ticks (positive integer literal)Server
@function_tag("namespace:path")Function tag registrationCaller / tag runtime
@on_trigger("x")Player runs /trigger xTriggering player
@on(EventType)Runtime-backed event firesEvent executor (Player for built-in events)
legacy @on_* event decoratorsParser compatibility onlyDo not use for runtime events
@schedule(ticks=N)Runs function once N ticks after datapack loadServer
@keep(Optimizer hint, no runtime effect)—
@coroutineMarks function as a coroutine (yields at loop back-edges)—
@inlineInline at call sites—
@deprecatedEmit warning on use—
@singletonStruct-backed global state—
@watchPer-player scoreboard changePlayer (@s)
@configNumeric compile-time config—
@profilePerformance timing—
@retry(N)Auto-retry N times—
@memoizeCache results—
@benchmarkTick-level timing—
@testTest function—

Released under the MIT License.