Event Handlers
The on.* namespace is how your component responds to things. Each hook registers a Roblox event connection (or signal subscriber) that Pulse manages and cleans up automatically.
The mental model is SolidJS createEffect — scoped to a specific Roblox event.
Frame events
on.heartbeat — every frame
on.heartbeat(() => {
// runs ~60 times per second (RunService.Heartbeat)
})
With options:
on.heartbeat({ when: enabled }, () => {
// skipped entirely when enabled() === false
})
on.heartbeat({ when: enabled, every: 0.1 }, () => {
// 10 times/sec instead of 60
})
on.heartbeat({ every: interval }, () => {
// interval is a signal — throttle adapts at runtime
})
Use for logic that needs to run every frame. Throttle with every if you don't need 60fps frequency — most ESP and detection loops are fine at 10fps.
on.renderStepped — before camera renders
on.renderStepped({ when: enabled }, () => {
// runs before each frame is rendered (RunService.RenderStepped)
})
Use for anything that affects what the player sees — camera manipulation, drawing updates, aimbot lock.
on.stepped — physics step
on.stepped(() => {
// RunService.Stepped — physics-related logic
})
Less common than heartbeat. Use when you need to align with Roblox's physics simulation step.
Character events
on.respawn / on.characterAdded
on.respawn(() => {
// fires when LocalPlayer.CharacterAdded
const h = _PulseGetHumanoid()
if (h) h.WalkSpeed = enabled() ? speed() : 16
})
on.characterAdded(() => {
// same as on.respawn
})
on.characterRemoving(() => {
// fires just before the character is destroyed
})
Use on.respawn to re-apply state after the player dies. The character helpers (_PulseGetChar, _PulseGetHumanoid, etc.) always reflect the current character — no caching needed.
Input events
on.inputBegan / on.inputEnded
on.inputBegan((input, gpe) => {
if (gpe) return // Roblox already handled it (typing in chat, menus)
if (input.KeyCode === Enum.KeyCode.E) {
// E was pressed
}
})
on.inputEnded((input, gpe) => {
if (input.UserInputType === Enum.UserInputType.MouseButton2) {
// right mouse button released
}
})
Always check gpe unless you want the handler to fire even when the user is typing.
Signal change
on.signal(enabled, (v) => {
// fires synchronously whenever enabled changes
if (v) startLoop()
else stopLoop()
})
Watch multiple signals:
on.signal(fov, applyFov)
on.signal(enabled, applyFov)
This fires synchronously — the handler runs before the .set() call returns. Keep handlers fast; use task.spawn for any async work.
One-shot delay
on.after(2.5, () => {
// runs once, 2.5 seconds after load
// automatically cancelled if the script is destroyed first
})
Equivalent to task.delay(2.5, fn) but lifecycle-aware.
when — conditional guard
on.heartbeat({ when: enabled }, () => {
// entire handler is skipped when enabled() === false
})
This is the core Pulse pattern. Feature logic only runs while the feature is active. Compiles to an early-return guard at the top of the handler.
every — throttle
on.heartbeat({ when: enabled, every: 1.0 }, () => {
// at most once per second
})
on.heartbeat({ every: interval }, () => {
// interval is a PulseSignal<number> — throttle adapts at runtime
})
Character helpers
Inside any on.* handler, use these globals instead of caching character references:
| Helper | Returns |
|---|---|
_PulseGetChar() | Character Model or undefined |
_PulseGetHRP() | HumanoidRootPart or undefined |
_PulseGetHumanoid() | Humanoid or undefined |
_PulseGetAlive() | boolean — true if humanoid.Health > 0 |
These read live from LocalPlayer.Character on every call. Never cache the result in a variable between calls — it becomes stale the moment the character is replaced.
// WRONG — stale after respawn
const cachedH = _PulseGetHumanoid()
on.heartbeat({ when: enabled }, () => {
if (cachedH) cachedH.WalkSpeed = speed()
})
// RIGHT — always read fresh
on.heartbeat({ when: enabled }, () => {
const h = _PulseGetHumanoid()
if (h) h.WalkSpeed = speed()
})