Fusion
Fusion copied to clipboard
Limiting state writing
Currently, set and get operations are present as methods on a single state object:
local value = Value(5)
value:set(15)
print(value:get()) -- 15
However, this poses a problem: what if we don't want to give full write permissions to someone we're giving our state objects to?
local function makeReadOnly()
local readOnly = Value(0)
task.defer(function()
repeat
readOnly:set(readOnly:get() + 1)
until false
end)
return readOnly
end
local readOnly = makeReadOnly()
print(readOnly:get()) -- ok
readOnly:set(9001) -- oh no - the contract is not enforced
Users end up having to get inventive with weird solutions like passthrough Computed objects, which aren't really great:
local function makeReadOnly()
local readOnly = Value(0)
task.defer(function()
repeat
readOnly:set(readOnly:get() + 1)
until false
end)
-- this would seem unnecessary to a casual reader
return Computed(function()
return readOnly:get()
end)
end
local readOnly = makeReadOnly()
print(readOnly:get()) -- ok
readOnly:set(9001) -- errors - that's better :)
It might be worth investigating some way of separating out the reading and writing abilities of a state object. One solution could be adopting React/Solid-like setter/getter splitting (open question: how does this translate to objects with more complex setting behaviour, for example a spring where we might want to read and write aspects like velocity and position?)
local function doLimited(getX)
-- can't change the value of x here, can only read from it
print("x is", getX())
end
local getX, setX = Value(10)
doLimited(getX) -- the value of x is 10
setX(25)
doLimited(getX) -- the value of x is 25
Another solution, perhaps more Fusion-like, would be introducing a utility ReadOnly function that creates a limited version of a state object for the user - this would almost certainly be optimised under the hood:
local function doLimited(x)
-- can't change the value of x here, can only read from it
print("x is", x:get())
end
local myValue = Value(10)
local limitedValue = ReadOnly(myValue)
doLimited(limitedValue) -- x is 10
myValue:set(25)
doLimited(limitedValue) -- x is 25
It's worth mentioning that a solution here could possibly relate partially to #35, as changing how we retrieve values could potentially also change how we treat constants versus state.
Another idea I like, though it doesn't address all the motivating problems, is the ability for value objects to give away write control over their value, turning the object itself read-only:
local foo = Value(5)
local access = foo:giveMeExclusiveWriteControl()
access:set(10)
print(peek(foo)) -- 10
foo:set(2) -- error
I think it makes more sense to do that as local foo, access = ReadOnly()
.
One-time operations on a State don't fit well in the reactive graph, and I can't imagine many cases where you wouldn't want to do it as one of the first things when a Value is created anyways.
I think it makes more sense to do that as
local foo, access = ReadOnly()
. One-time operations on a State don't fit well in the reactive graph, and I can't imagine many cases where you wouldn't want to do it as one of the first things when a Value is created anyways.
Mostly I'm thinking of use cases where you pass a value into a function call with the expectation that the function call starts maintaining that value for you, in which case it becomes a way of enforcing strongly that two agents don't have write access at the same time. Think e.g. [Ref]
.