Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sanchedev/tiny-engine/llms.txt

Use this file to discover all available pages before exploring further.

Signal<T> is the reactive primitive that powers state management in tiny-engine. When a signal’s value changes, every registered subscriber is called synchronously with the new value. Hooks like useSignal are thin wrappers around Signal designed for JSX components; the raw Signal class is what you use inside TinyScript classes and standalone game logic.

new Signal<T>(initialValue)

Creates a signal with the given initial value. The type parameter T is inferred from the initial value.
import { Signal } from 'tiny-engine'

const health = new Signal(100)        // Signal<number>
const name   = new Signal('Player')   // Signal<string>
const alive  = new Signal(true)       // Signal<boolean>
initialValue
T
required
The starting value of the signal. Determines the signal’s type.

.value

A getter/setter pair for reading and writing the signal’s current value.
const score = new Signal(0)

console.log(score.value)  // 0

score.value = 42          // notifies all subscribers
console.log(score.value)  // 42
Writing the same value that is already stored is a no-op — subscribers are only notified when the value actually changes (val !== this.#value).

.sub(fn)

Registers a subscriber that is called every time the signal’s value changes.
const health = new Signal(100)

health.sub((val) => {
  console.log('Health is now:', val)
})

health.value = 80  // logs: "Health is now: 80"
fn
(value: T) => void
required
The callback to invoke on each change. Receives the new value as its only argument.
Unlike useSignal, .sub() does not return an unsubscribe function — use .unsub(fn) to remove a specific listener, or .clearSubs() to remove all of them.

.unsub(fn)

Removes a previously registered subscriber. You must pass the same function reference that was passed to .sub().
const mana = new Signal(100)

const onManaChange = (val: number) => console.log('Mana:', val)

mana.sub(onManaChange)
// ... later
mana.unsub(onManaChange)

mana.value = 50  // no log — listener was removed
fn
(value: T) => void
required
The exact callback reference to remove.

.clearSubs()

Removes all subscribers from this signal at once. Useful during scene teardown to prevent stale callbacks from firing after a node is destroyed.
const damage = new Signal(0)

damage.sub((val) => console.log('Damage:', val))
damage.value = 25  // logs: "Damage: 25"

damage.clearSubs()
damage.value = 50  // no log — all subs cleared

SignalGetter<T>

A type alias for a zero-argument function that returns the current signal value:
interface SignalGetter<T> {
  (): T
}
SignalGetter<T> is returned as the first element of the useSignal tuple and acts as both a read accessor and a dependency tracker for reactive JSX expressions. Any JSX attribute that is a function () => T is automatically treated as a reactive binding — it re-evaluates whenever the underlying signal changes.

SignalSetter<T>

A type alias for a function that updates the signal value:
interface SignalSetter<T> {
  (value: T): void
}
SignalSetter<T> is the second element of the useSignal tuple. Calling it is equivalent to assigning signal.value = newValue.

Signal vs useSignal

ScenarioUse
Logic inside a TinyScript classnew Signal<T>(initial)
Shared state between multiple scriptsnew Signal<T>(initial) (module-level)
Reactive state in a JSX componentuseSignal(initial)
Exposing readable state to JSXReturn a SignalGetter from a script method
useSignal is implemented on top of Signal and adds integration with the JSX component lifecycle. Outside of JSX components, prefer Signal directly.

Example: TinyScript with reactive health

import { TinyScript } from 'tiny-engine/scripts'
import { Signal } from 'tiny-engine'
import { PrimaryNode } from 'tiny-engine'

class PlayerScript extends TinyScript<PrimaryNode.Transform> {
  health = new Signal(100)

  setup() {
    // Sync a UI label whenever health changes
    this.health.sub((hp) => {
      hudHealthLabel.value = `HP: ${hp}`
    })

    this.connect('destroyed', () => {
      // Clean up to prevent memory leaks after the node is gone
      this.health.clearSubs()
    })
  }

  takeDamage(amount: number) {
    this.health.value = Math.max(0, this.health.value - amount)
    if (this.health.value === 0) {
      this.me.destroy()
    }
  }
}

Example: Reactive JSX attribute

import { useSignal } from 'tiny-engine/hooks'

function HealthBar() {
  const [hp, setHp] = useSignal(100)

  return (
    <rect
      width={() => hp() * 2}   // re-evaluates whenever hp changes
      height={10}
      fill={[0.2, 0.8, 0.2, 1]}
    />
  )
}
Any JSX prop that is a function (() => T) is treated as a reactive binding and re-evaluated whenever the signals it reads change. Static values (e.g., plain numbers or strings) are bound once and never update.

Build docs developers (and LLMs) love