Skip to content

Computed state object since v0.1

Calculates a single value based on the returned values from other state objects.

(
    processor: () -> (T, M),
    destructor: ((T, M) -> ())?
) -> Computed<T, M>

Parameters

  • processor: () -> (T, M) - computes and returns values to be returned from the computed object, optionally returning extra values for the destructor alone

  • destructor: ((T, M) -> ())? - disposes of values generated by processor when they are no longer in use


Object Methods

since v0.1

Computed:get()

Returns the current value stored in the state object.

If dependencies are being captured (e.g. inside a computed callback), this state object will also be added as a dependency.

(asDependency: boolean?) -> T

Example Usage

local numCoins = State(50)

local doubleCoins = Computed(function()
    return numCoins:get() * 2
end)

print(doubleCoins:get()) --> 100

numCoins:set(2)
print(doubleCoins:get()) --> 4

Dependency Management

Computed objects automatically detect dependencies used inside their callback each time their callback runs. This means, when you use a function like :get() on a state object, it will register that state object as a dependency:

local numCoins = Value(50)

local doubleCoins = Computed(function()
    -- Fusion detects you called :get() on `numCoins`, and so adds `numCoins` as
    -- a dependency of this computed object.
    return numCoins:get() * 2
end)

When a dependency changes value, the computed object will re-run its callback to generate and cache the current value internally. This value is later exposed via the :get() method.

Something to note is that dependencies are dynamic; you can change what values your computed object depends on, and the dependencies will be updated to reduce unnecessary updates:

local stateA = Value(5)
local stateB = Value(5)
local selector = Value("A")

local computed = Computed(function()
    print("> updating computed!")
    local selected = selector:get()
    if selected == "A" then
        return stateA:get()
    elseif selected == "B" then
        return stateB:get()
    end
end)

print("increment state A (expect update below)")
stateA:set(stateA:get() + 1)
print("increment state B (expect no update)")
stateA:set(stateA:get() + 1)

print("switch to select B")
selector:set("B")

print("increment state A (expect no update)")
stateA:set(stateA:get() + 1)
print("increment state B (expect update below)")
stateA:set(stateA:get() + 1)
> updating computed!
increment state A (expect update below)
> updating computed!
increment state B (expect no update)
switch to select B
> updating computed!
increment state A (expect no update)
increment state B (expect update below)
> updating computed!

Pitfall: using non-state values

Stick to using state objects and computed objects inside your computations. Fusion can detect when you use these objects and listen for changes.

Fusion can't automatically detect changes when you use 'normal' variables:

local theVariable = "Hello"
local badValue = Computed(function()
    -- don't do this! use state objects or computed objects in here
    return "Say " .. theVariable
end)

print(badValue:get()) -- prints 'Say Hello'

theVariable = "World"
print(badValue:get()) -- still prints 'Say Hello' - that's a problem!

By using a state object here, Fusion can correctly update the computed object, because it knows you used the state object:

local theVariable = Value("Hello")
local goodValue = Computed(function()
    -- this is much better - Fusion can detect you used this state object!
    return "Say " .. theVariable:get()
end)

print(goodValue:get()) -- prints 'Say Hello'

theVariable:set("World")
print(goodValue:get()) -- prints 'Say World'

This also applies to any functions that change on their own, like os.clock(). If you need to use them, store values from the function in a state object, and update the value of that object as often as required.


Destructors

The destructor callback, if provided, is called when the computed object swaps out an old value for a newly-generated one. It is called with the old value as the first parameter, and - if provided - an extra value returned from processor as a customisable second parameter.

Destructors are required when working with data types that require destruction, such as instances. Otherwise, they are optional, so not all calculations have to specify destruction behaviour.

Fusion guarantees that values passed to destructors by default will never be used again by the library, so it is safe to finalise them. This does not apply to the customisable second parameter, which the user is responsible for handling properly.

Back to top