Scopes and feature lifecycle
A module is loaded once, but an individual feature may be enabled and disabled many times. Creating a new timer or event subscription on every enable without cancelling the old one causes duplicate callbacks. World visuals can remain on screen for the same reason.
A scope is a small container for everything owned by one active feature.
Create it when the feature starts, register its resources, and call
dispose() when the feature stops.
Minimal example
Section titled “Minimal example”import { scheduler, scope } from "@killscript/sdk/client";
const active = scope();
active.using(scheduler.every(0.25, () => { // Update the HUD from current game state.}));active.defer(() => print("HUD feature stopped"));
// Later, when the feature is disabled:active.dispose();using() accepts an EventSubscription, such as the result of a scheduler or
event watcher. defer() stores an ordinary cleanup callback for state that the
SDK cannot reset automatically.
| Method | What it owns or does |
|---|---|
using() | A subscription or task with Cancel() |
defer() | A cleanup callback supplied by your code |
world() | A client object created through WorldVisuals |
dispose() | Releases every resource stored by the scope |
isDisposed() | Reports whether this scope has already been disposed |
A toggleable feature
Section titled “A toggleable feature”Keep the current scope next to the feature state. Each activation receives a fresh scope; each deactivation disposes exactly that instance:
import { type ClientScope, scheduler, scope, visuals,} from "@killscript/sdk/client";
let active: ClientScope | undefined;
export function enableMarker(getPosition: () => Vector3 | undefined): void { if (active !== undefined) return;
const position = getPosition(); if (position === undefined) return;
const next = scope(); const marker = next.world(visuals.overlay({ position: position, size: new Vector3(0.8, 0.02, 0.8), color: new Color(0, 1, 1, 0.55), })); if (marker === undefined) { next.dispose(); return; }
next.using(scheduler.every(0.25, () => { const current = getPosition(); if (current !== undefined) marker.SetPosition(current); })); active = next;}
export function disableMarker(): void { active?.dispose(); active = undefined;}Client scopes add world(). It returns the same object, but also remembers to
remove it through WorldVisuals during disposal. This is useful for visuals
that should live exactly as long as a feature.
What disposal does
Section titled “What disposal does”When dispose() is called, the scope performs these steps once:
- cancels tracked subscriptions in reverse order;
- removes tracked client world objects in reverse order;
- runs deferred callbacks in reverse order.
Calling dispose() again is safe and does nothing. Adding a subscription,
world object or callback to an already disposed scope cleans it immediately,
which prevents a late asynchronous result from leaking a resource.
Reflex exposes the same scope() and the same using()/defer() behavior,
but not world() because WorldVisuals is client-only.
See Scheduling for cancellable jobs and World visuals for objects a client scope can own.