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>
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
processorwhen they are no longer in use
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
local numCoins = State(50) local doubleCoins = Computed(function() return numCoins:get() * 2 end) print(doubleCoins:get()) --> 100 numCoins:set(2) print(doubleCoins:get()) --> 4
Computed objects automatically detect dependencies used inside their callback
each time their callback runs. This means, when you use a function like
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
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.
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
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.