Skip to main content

Components

A component is the basic unit in Pulse. One .ts file = one feature = one component.

The mental model is SolidJS: reactive signals for state, on.* hooks for effects, and a return value that declares the UI.


The structure

defineComponent('SpeedHack', () => {
// 1. Signals — reactive state
const enabled = signal(false)
const speed = signal(16)

// 2. Local variables — non-reactive internal state
let lastFrame = 0

// 3. Event hooks — like createEffect scoped to a Roblox event
on.heartbeat({ when: enabled }, () => {
const h = _PulseGetHumanoid()
if (h) h.WalkSpeed = speed()
})

on.respawn(() => {
const h = _PulseGetHumanoid()
if (h) h.WalkSpeed = enabled() ? speed() : 16
})

// 4. Return the UI widgets
return [
toggle('Speed Hack').bind(enabled),
slider('Walk Speed', { min: 16, max: 250 }).bind(speed),
]
})

Signals

const enabled = signal(false) // boolean
const speed = signal(16) // number
const mode = signal('Normal') // string

Read by calling enabled(), write with enabled.set(true). See Signals for the full API.


Event hooks

Frame events

on.heartbeat(opts?, fn) // RunService.Heartbeat — every frame
on.renderStepped(opts?, fn) // RunService.RenderStepped — before render
on.stepped(opts?, fn) // RunService.Stepped — physics step

Options:

{ when: PulseSignal<boolean> } // only run when signal is truthy
{ every: number | PulseSignal<number> } // throttle in seconds

Character events

on.characterAdded(fn) // LocalPlayer.CharacterAdded
on.characterRemoving(fn) // LocalPlayer.CharacterRemoving
on.respawn(fn) // shorthand for CharacterAdded

Input events

on.inputBegan((input, gpe) => {
if (gpe) return // Roblox already handled it (chat, menus)
if (input.KeyCode === Enum.KeyCode.E) {
// E pressed
}
})

on.inputEnded((input, gpe) => { ... })

Signal change

on.signal(enabled, (v) => {
if (v) startLoop()
else stopLoop()
})

One-shot delay

on.after(2.5, () => {
// runs once, 2.5 seconds after load
})

Character helpers

Inside any on.* handler, use these globals:

const char = _PulseGetChar() // character Model | undefined
const hrp = _PulseGetHRP() // HumanoidRootPart | undefined
const h = _PulseGetHumanoid() // Humanoid | undefined
const alive = _PulseGetAlive() // boolean

These read live from LocalPlayer.Character on every call — never stale, never need to be refreshed.

on.heartbeat({ when: enabled }, () => {
const h = _PulseGetHumanoid()
if (!h) return // dead or no character
h.WalkSpeed = speed()
})

UI widgets

Return an array of widget builders from defineComponent:

return [
toggle('Speed Hack').bind(enabled),
slider('Walk Speed', { min: 16, max: 250 }).bind(speed),
dropdown('Mode', { options: ['Normal', 'Rage'] }).bind(mode),
button('Reset', () => speed.set(16)),
keybind('Activate', { default: 'F' }).bind(enabled),
separator(),
label('Advanced'),
]

See Building UI for the full widget reference.


Lifecycle

  1. LoaddefineComponent setup function runs, signals are created, on.* hooks are registered
  2. Running — hooks fire in response to Roblox events and signal changes
  3. Destroy_PulseDestroy() is called: all connections disconnect, all watchers unsubscribe

There is no explicit unmount hook. Cleanup is handled automatically by the framework when _PulseDestroy runs.


Accessing another component

Every component is a global by its name:

// From inside another component:
SpeedHack.enabled.set(true) // write its signal
const v = SpeedHack.speed() // read its signal

SpeedHack.enabled.watch((v) => { // subscribe to changes
...
})

What you cannot do

No require() — the output is a flat Lua file. No module system. Put shared helpers in globals.lua.

No async in signal watcherson.signal fires synchronously. Don't await inside it. Use task.spawn for async work:

on.signal(enabled, (v) => {
if (v) {
task.spawn(() => {
// async work here
})
}
})

No Roblox method calls with dot notation — Roblox Instance methods need colon syntax in Lua. TSTL with noImplicitSelf compiles obj.method() as dot notation. Use the Pulse character helpers (_PulseGetChar, _PulseGetHumanoid, etc.) instead of calling methods on character objects directly.