A single-file reimplementation of alien-signals in Luau.
denpa (or alien-signals in general) is an extremely fast and lightweight library for managing state and reactivity, exploring a push-pull based signal algorithm. The name of this repository originates from the Japanese word 電波 [den.pa] which literally translates to "radio waves" and can refer to people who claim they receive thoughts from outside or alien signals.
The implementation is currently up-to-date with v3.2.1 of alien-signals.
test is a port of johnsoncodehk/reactive-framework-test-suite, a framework-agnostic test suite for comparing reactive framework behavior. The suite focuses on reactive semantics (propagation, batching, disposal, edge cases), not API completeness. Tests that require an optional capability are skipped for frameworks that don't expose it, rather than marked as failures.
You can run this test by running lute run test in the terminal at the repository location.
bench is a port of transitive-bullshit/js-reactivity-benchmark, a framework-agnostic benchmark suite for measuring and comparing reactive framework performance.
You can run this benchmark by running lute run bench in the terminal at the repository location.
Install denpa either by appending anothersubatomo/denpa@[CHOSEN_VERSION] to the dependencies at your wally.toml file or copypasting the source, as it is a single-file library.
Use signal to create a mutable reactive state. Use computed to create a derived state. The derived state updates when its dependent signals change. Use effect to execute a function. The given function re-executes when its dependent signals change:
const signals = require("denpa")
const signal = signals.signal
const computed = signals.computed
const effect = signals.effect
const count = signal(1)
const doubleCount = computed(function()
return count() * 2
end)
effect(function()
print(`Count is: {count()}`)
end) -- print: "Count is: 1"
print(doubleCount()) -- 2
count(2) -- print: "Count is: 2"
print(doubleCount()) -- 4Use effectScope to group multiple effects. The effectScope function returns a stop function. Call the stop function to clean up all effects in the group. The system does not execute these effects again:
const signals = require("denpa")
const signal = signals.signal
const effect = signals.effect
const effectScope = signals.effectScope
const count = signal(1)
const stopScope = effectScope(function()
effect(function()
print(`Count in scope: {count()}`)
end) -- print: "Count in scope: 1"
end)
count(2) -- print: "Count in scope: 2"
stopScope()
count(3) -- no printYou can nest an effect inside another effect. The outer effect always runs before its inner effects. When the outer effect runs again, the system removes the inner effects from the previous run. The system creates new inner effects if the code requires them:
const signals = require("denpa")
const signal = signals.signal
const effect = signals.effect
const show = signal(true)
const count = signal(1)
effect(function()
if show() then
-- this inner effect is created when show() is true
effect(function()
print(`Count is: {count()}`)
end)
end
end) -- print: "Count is: 1"
count(2) -- print: "Count is: 2"
-- when show becomes false, the inner effect is cleaned up
show(false) -- no print
count(3) -- no print (inner effect no longer exists)Direct changes to a signal value do not automatically trigger updates. Use the trigger function to manually start the update propagation. You can trigger a single signal. You can also trigger multiple signals with one call by putting their read functions inside a trigger function:
const signals = require("denpa")
const signal = signals.signal
const computed = signals.computed
const trigger = signals.trigger
const arr = signal({})
const length = computed(function()
return #arr()
end)
print(length()) -- 0
-- direct mutation doesn't automatically trigger updates
table.insert(arr(), 1)
print(length()) -- still 0
-- manually trigger updates
trigger(arr)
print(length()) -- 1You can also trigger multiple signals at once:
const signals = require("denpa")
const signal = signals.signal
const computed = signals.computed
const trigger = signals.trigger
const src1 = signal({})
const src2 = signal({})
const total = computed(function()
return #src1() + #src2()
end)
table.insert(src1(), 1)
table.insert(src2(), 2)
trigger(function()
src1()
src2()
end)
print(total()) -- 2