Skip to main content

Pulse.Store

Pulse.Store is a reactive key-value store shared across all components. Use it when state genuinely belongs to more than one component and a signal on one component would create awkward coupling.


When to use it vs a signal

Use a signalUse Pulse.Store
State belongs to one componentState has no clear single owner
Only that component changes itMultiple components read AND write it
Other components only read itMultiple components need to react to changes

If you're accessing OtherComponent.signal() from 4 different components, that's often a sign the value should be in Pulse.Store.


API

-- Define a key with a default (call once per key)
Pulse.Store.define("GlobalTarget", nil)

-- Read the current value
local target = Pulse.Store.get("GlobalTarget")

-- Write a new value (triggers all watchers)
Pulse.Store.set("GlobalTarget", someModel)

-- React to changes
Pulse.Store.watch("GlobalTarget", function(newValue)
-- called every time the value changes
end)

-- Get the raw Signal object
local sig = Pulse.Store.signal("GlobalTarget")
sig:onChange(function(v) ... end)

Defining keys

Call Pulse.Store.define in component setup. Define each key once — usually in the component that conceptually owns it:

defineComponent('Targeter', () => {
Pulse.Store.define('GlobalTarget', null)
Pulse.Store.define('ScanMode', 'Normal')
// ...
})

Practical example

Two components — Targeter locks onto a target, NapeSize reads the locked target to expand its hitbox:

// Targeter component
defineComponent('Targeter', () => {
Pulse.Store.define('LockedTarget', null)

function lockNearest() {
const t = Pulse.Aim.findNearest(...)
Pulse.Store.set('LockedTarget', t)
}

return [button('Lock Nearest', lockNearest)]
})

// NapeSize component — reads the locked target
defineComponent('NapeSize', () => {
const loop = Pulse.Loop.new(1.0, () => {
const target = Pulse.Store.get('LockedTarget')
for (const titan of func.GetCachedTitans()) {
const isTarget = (titan === target)
// apply bigger size if this is the locked target
}
})
// ...
})

NapeSize never imports or depends on Targeter — it just reads the store key. Either component can be removed without breaking the other.


Limitations

  • Keys must be defined before they're used. Calling get on an undefined key returns nil with no warning.
  • watch callbacks run synchronously when set is called — same rules as signals (don't yield).
  • There's no schema or type checking. Conventions (always a Model, always a string) are up to you.