Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

README.md

actions

A name-registered action registry over Zin.bindings + Zin.context. A name maps to a list of binding descriptors and one declared context; polling queries (held, pressed, released, value, heldTime, repeated) evaluate those bindings against the live snapshot, and the handler surface (bind / onPressed / onReleased / onChanged / onHeld) fires the same state from inside Zin.tick, after axes and chords advance.

A control asset replaced this. A <name>.inputBinding/ folder inside an .inputMap/ carries the label, the kind, and a binding for keyboard-and-mouse, gamepad and touch, and a world activates the map and subscribes to the controls it uses:

public = {
    inputMap = Field.assetRef("inputMap", "@builtin::inputMaps.default", NoSync, "The controls this component reads."),
    speed = Field.number(5, NoSync, "World units per second at full deflection."),
    jumps = Field.number(0, NoSync, "How many times the jump control was pressed."),
}

local controls

function awake()
    controls = public.inputMap:activate()

    controls.move:onInput(function(v, dt, active)
        if not active then return end
        local p = public.entity.position
        local step = public.speed * dt
        public.entity.position = { p.x + v.x * step, p.y, p.z - v.y * step }
    end)

    controls.jump:onPressed(function() public.jumps += 1 end)
end

function onDestroy()
    public.inputMap:deactivate(controls)
end

Zin.map.bake(name) writes the currently active map out as an .inputMap asset. man topics/input covers the control model.

Exports

Registry:

  • M.define(spec: ActionSpec) — define / replace actions.
  • M.remove(name: string) — remove one action (also tears down handlers).
  • M.clear() — wipe every action + handler.
  • M.has(name: string) -> boolean
  • M.names() -> { string }
  • M.get(name: string) -> ActionEntry? — the registry record behind a name.
  • M.active(name: string) -> boolean — true iff the action's context matches the top of the stack.

Polling queries (gated by context):

  • M.held(name) / pressed(name) / released(name) -> boolean
  • M.value(name) -> number | Vec2 | 0/1
  • M.heldTime(name) -> number?
  • M.repeated(name, opts?) -> boolean

Event-driven handlers:

  • M.bind(name, fn, opts?) -> ActionHandle? — register a handler.
  • M.disconnect(handle: ActionHandle) -> boolean
  • M.onPressed / onReleased / onChanged / onHeld(name, fn, opts?) -> ActionHandle?
  • M.handlerCount(name) -> number

Internal hooks (wired by zinput/init.luau):

  • M._setAllocator(fn: () -> number) — shared id allocator.
  • M._setEnsureBindingsFn(fn) / M._setEnsureLiveFn(fn) — the cold-read hooks.
  • M._owns(handle: number) -> boolean
  • M._dispatchHandlers(firstTickThisFrame: boolean?) — called by Zin.tick.
  • M._clearHandlers() — test isolation.

Types:

  • ActionEntry = { context: string, bindings: { any } }
  • ActionSpec = { [string]: any } — define-spec entry shapes.
  • ActionState = "Begin" | "Change" | "End" | "Held"
  • BindOpts = { priority: number?, fire: { ActionState }?, once: boolean? }
  • ActionHandle = number

Notes

  • Define-spec shorthand:
    • binding — single binding under the default context.
    • { binding1, binding2 } — multiple bindings under the default context.
    • { context = "...", binding = ... } / { context, bindings = {...} } — explicit context.
  • Context gating is strict: the action's context string must equal Zin.context.current(). There is no inheritance up the stack.
  • Handlers run from Zin.tick after axes/chords advance. With no tick host (or Zin.autoTick.start()), handlers never fire.
  • Returning "sink" from a handler stops further (lower-priority) handlers on the same action this tick. Anything else continues.
  • A handler also ends with the context that bound it: one a component binds ends when that component instance goes (removed, hot-reloaded, or its entity despawned), one a module body binds at the module's next run. scopes.list() names each as a luau row under the context it follows.
  • remove(name) and clear() also wipe handlers bound to those actions — handler state never outlives its action.
  • The handler allocator is swapped at boot (_setAllocator) so handles drawn here share one namespace with Zin.input.on*.