pesde wiam / swan

Concepts

States

A state is a container for a value. Internally, it's a table, with the fields:

  • .value: The state's current value. Read and write this if you don't intend to track this state or trigger effects.
  • .always_force: Whether every write operation for this state is automatically forced.
  • .callbacks: List of functions bound to this state (effects).

and a metatable which implements the metamethods:

  • __call(): For reading, with tracking.
  • __call(value, force): For writing, triggering effects, optionally forcing.
  • as well as __concat() and every mathematical metamethod, for implicit deriveds.

Implicit deriveds

Deriveds are a way of making a state's value depend on another's.

swan lets you do this by using either swan.derived(), for complex operations, or by directly using Luau operators on them.

Using operators such as .., +, / etc. on a string or number state will automatically create a new state whose value will be the result of said operation on the initial state.

Here is a simple example:

local ammo = swan.state(20)
local ammo_ui = "Ammo: "..ammo :: any

print(ammo_ui()) -- "Ammo: 20"
ammo(10)
print(ammo_ui()) -- "Ammo: 10"

local xp = swan.state(300)
local bars = xp / 10 :: any

print(bars()) -- 30
xp(500)
print(bars()) -- 50

Do note that currently, because of some Luau bugs, the metamethods aren't properly typed, so you'll have to cast to any derived states to suppress type errors.

Table states

If you store a table within a state, swan will detect changes only when the table itself (the identity) changes.
If you instead would like to react to the internal changes of a table, including nested ones, you can use swan.proxy_state().

Info

Given the way swan does this, which involves creating a proxy table with some metamethods, functions like pretty printers might not show the contents of the table, but they are there!
To avoid this behaviour, you can use utils.get_real() and utils.state_get_real().

Effects

An effect is a binding which will automatically trigger its function whenever one of its tracked states get updated (or forced).

Forcing

By default, effects only trigger when one of their tracked states change values, but this can be changed by forcing.

Tracking

Tracking is the mechanism used internally by swan to determine which states get bound to an effect.

Branching

Given the way tracking works, which involves triggering effects once when they are made, states read within alternate branches of an if statement don't get tracked.
To solve this, you can either read the states at the top-level of the effect, or use utils.branch().

Roblox

Despite being a runtime-agnostic library, swan provides some functionality to facilitate development on Roblox.