This commit is contained in:
Aaron Smith 2023-09-12 17:55:54 +01:00
parent 5a458eae71
commit 68ff117be9
19 changed files with 80 additions and 83 deletions

View file

@ -50,16 +50,16 @@ Creates a new source with the given value.
count(count() + 1) -- 1 count(count() + 1) -- 1
``` ```
## watch() ## effect()
Runs a callback on source update. Runs a callback on source update.
- **Type** - **Type**
```lua ```lua
function watch(source: () -> ()): Unwatch function effect(source: () -> ()): Uneffect
type Unwatch = () -> () type Uneffect = () -> ()
``` ```
- **Details** - **Details**
@ -70,7 +70,7 @@ Runs a callback on source update.
Any time a source referenced in the callback is changed, the callback will Any time a source referenced in the callback is changed, the callback will
be reran. be reran.
Also returns a function that when called, stops the watcher immediately. Also returns a function that when called, stops the effecter immediately.
::: warning ::: warning
`source()` cannot yield. `source()` cannot yield.
@ -81,7 +81,7 @@ Runs a callback on source update.
```lua ```lua
local state = source(1) local state = source(1)
watch(function() effect(function()
print(state()) print(state())
end) end)

View file

View file

@ -15,7 +15,7 @@ Runs a callback anytime a reactive scope is re-ran.
```lua ```lua
local data = source(1) local data = source(1)
watch(function() effect(function()
local label = create "TextLabel" { Text = data() } local label = create "TextLabel" { Text = data() }
cleanup(function() cleanup(function()

View file

@ -12,14 +12,14 @@ and identifying improper usage.
Currently, strict mode will: Currently, strict mode will:
1. Run derived sources twice a source updates. 1. Run derived sources twice a source updates.
2. Run watchers twice when a source updates. 2. Run effecters twice when a source updates.
3. Throw an error if yields occur where they are not allowed. 3. Throw an error if yields occur where they are not allowed.
4. Checks for `indexes()` and `values()` returning primitive values. 4. Checks for `indexes()` and `values()` returning primitive values.
5. Checks for duplicate nested properties at same depth. 5. Checks for duplicate nested properties at same depth.
6. Better error reporting and stack traces. 6. Better error reporting and stack traces.
7. Checks for multiple `cleanup()` calls in the same function scope. 7. Checks for multiple `cleanup()` calls in the same function scope.
By rerunning sources and watchers, any side-effects are made more apparent. By rerunning sources and effecters, any side-effects are made more apparent.
This also helps ensure that cleanups are being handled correctly. This also helps ensure that cleanups are being handled correctly.
Accidental yielding within reactive scopes can break Vide's reactive graph, Accidental yielding within reactive scopes can break Vide's reactive graph,

View file

@ -3,7 +3,7 @@
Instances are created using [`create()`](../../api/creation.md#create). Instances are created using [`create()`](../../api/creation.md#create).
```lua ```lua
local vide = require(path_to_vide) local vide = require(vide)
local create = vide.create local create = vide.create
``` ```

View file

@ -5,7 +5,10 @@ Components are custom-made reusable pieces of UI made from other pieces of UI.
By using components you can make your application more modular and better By using components you can make your application more modular and better
organized. organized.
```lua [Button.lua] ```lua [Button.luau]
local vide = require(vide)
local create = vide.create
local function Button(props: { local function Button(props: {
Position: UDim2, Position: UDim2,
Text: string, Text: string,
@ -24,7 +27,10 @@ end
return Button return Button
``` ```
```lua [App.lua] ```lua [App.luau]
local vide = require(vide)
local create = vide.create
local Button = require(Button) local Button = require(Button)
local function App() local function App()
@ -34,7 +40,7 @@ local function App()
Text = "click me!", Text = "click me!",
Activated = function() Activated = function()
print "clickeD" print "clicked"
end end
} }
} }
@ -43,27 +49,17 @@ end
root(App).Parent = game.StarterGui root(App).Parent = game.StarterGui
``` ```
Above is a simple example of a button component with its background color set to Above is a simple example of a button component with a set color and size,
a dark grey and with a fixed size. being reused across files.
A single parameter `props` is used to pass properties to the component. A single parameter `props` is used to pass properties to the component.
Creating instances of this button component is as simple as doing the below:
```lua
local button = Button {
Position = UDim2.new(),
Text = "Click me!",
Activated = function()
print "clicked"
end
}
```
Components allow you to *encapsulate* behavior. You can only modify the Components allow you to *encapsulate* behavior. You can only modify the
component in ways that you allow in the component. component in ways that you allow in the component.
This also promotes code reusability. Anytime you want a new button all you do This also promotes code reusability. Anytime you want a new button all you do
is call `Button {}` instead of creating and setting every property each time. is call `Button {}` instead of creating and setting every property each time.
When changing the button in future, any changes to the button file will be
reflected anywhere the button is used throughout your app.
This can be extended to much more complicated UI. This can be extended to much more complicated UI.

View file

@ -9,7 +9,6 @@ A source in Vide can be created using
```lua ```lua
local source = vide.source local source = vide.source
local count = source(0) local count = source(0)
``` ```
@ -24,7 +23,10 @@ count(count() + 1) -- increment source by 1
Below is an example of a stateful counter component. Below is an example of a stateful counter component.
```lua ```lua [Counter.luau]
local vide = require(vide)
local source = vide.source
local function Counter(props: { Position: UDim2 }) local function Counter(props: { Position: UDim2 })
local count = source(0) local count = source(0)
@ -54,22 +56,3 @@ This allows you as the programmer to not need to manually update GUI as the stat
of your program changes. You just define how the data maps to UI, and Vide's of your program changes. You just define how the data maps to UI, and Vide's
reactive system will surgically update any properties depending on sources that reactive system will surgically update any properties depending on sources that
are changed. are changed.
Since sources are just functions, you can also pass external sources to
components like so:
```lua
local function Text(p: {
Text: () -> string
})
return create "TextLabel" {
Text = p.Text
}
end
local text = source "hi"
Text {
Text = text
}
```

View file

View file

@ -63,7 +63,7 @@ callback when deriving or binding sources. If a source is in a function but is
never referenced the first time it runs, Vide will not know to rerun the never referenced the first time it runs, Vide will not know to rerun the
function if that source changes. function if that source changes.
An example to watch out for is when using sources within branches: An example to effect out for is when using sources within branches:
```lua ```lua
local condition = source(true) local condition = source(true)

View file

18
src/changed.luau Normal file
View file

@ -0,0 +1,18 @@
if not game then script = require "test/relative-string" end
local action = require(script.Parent.action)()
local cleanup = require(script.Parent.cleanup)
local function changed<T>(property: string, callback: (T) -> ())
return action(function(instance)
local con = instance:GetPropertyChangedSignal(property):Connect(function()
callback((instance :: any)[property])
end)
cleanup(function()
con:Disconnect()
end)
end)
end
return changed

View file

@ -8,9 +8,9 @@ local open_scope = graph.open_scope
local close_scope = graph.close_scope local close_scope = graph.close_scope
local set_owner = graph.set_owner local set_owner = graph.set_owner
local function watch<T>(effect: (T) -> T, initial_value: T) local function effect<T>(effect: (T) -> T, initial_value: T)
local owner = get_scope() local owner = get_scope()
if not owner then throw("cannot watch in non-reactive scope") end if not owner then throw("cannot effect in non-reactive scope") end
assert(owner) assert(owner)
local node = create_node(initial_value) local node = create_node(initial_value)
@ -24,4 +24,4 @@ local function watch<T>(effect: (T) -> T, initial_value: T)
close_scope() close_scope()
end end
return watch :: (<T>(effect: (T) -> T, initial_value: T) -> ()) & ((effect: () -> ()) -> ()) return effect :: (<T>(effect: (T) -> T, initial_value: T) -> ()) & ((effect: () -> ()) -> ())

View file

@ -41,7 +41,7 @@ local check_for_yield: <T...>(fn: (T...) -> unknown, T...) -> () do
if not ok then if not ok then
if err == "attempt to yield across metamethod/C-call boundary" or err == "thread is not yieldable" then if err == "attempt to yield across metamethod/C-call boundary" or err == "thread is not yieldable" then
throw(EVALUATION_ERR .. "cannot yield when deriving node in watcher") throw(EVALUATION_ERR .. "cannot yield when deriving node in effecter")
else else
throw(EVALUATION_ERR .. err) throw(EVALUATION_ERR .. err)
end end

View file

@ -9,7 +9,7 @@ local root = require(script.root)
local create = require(script.create) local create = require(script.create)
local apply = require(script.apply) local apply = require(script.apply)
local source = require(script.source) local source = require(script.source)
local watch = require(script.watch) local effect = require(script.effect)
local cleanup = require(script.cleanup) local cleanup = require(script.cleanup)
local untrack = require(script.untrack) local untrack = require(script.untrack)
local derive = require(script.derive) local derive = require(script.derive)
@ -52,7 +52,7 @@ local vide = {
root = root, root = root,
create = create, create = create,
source = source, source = source,
watch = watch, effect = effect,
derive = derive, derive = derive,
indexes = indexes, indexes = indexes,
values = values, values = values,

View file

@ -37,12 +37,12 @@ local function main()
local source = vide.source local source = vide.source
local spring = vide.spring local spring = vide.spring
local watch = vide.watch local effect = vide.effect
local value = source(MAX) local value = source(MAX)
local sprung = spring(value, 1, 0.3) local sprung = spring(value, 1, 0.3)
watch(function() effect(function()
local v = sprung() local v = sprung()
local fv = math.floor(v) local fv = math.floor(v)
local reset = "\27[H\27[2J" -- ANSI clear terminal local reset = "\27[H\27[2J" -- ANSI clear terminal

View file

@ -261,7 +261,7 @@ end)
TEST("source()", wrap_root(function() TEST("source()", wrap_root(function()
local source = vide.source local source = vide.source
local watch = vide.watch local effect = vide.effect
do CASE "create source" do CASE "create source"
local src = source(1) local src = source(1)
@ -278,7 +278,7 @@ TEST("source()", wrap_root(function()
local src = source(1) local src = source(1)
local count = 0 local count = 0
watch(function() effect(function()
src() src()
count += 1 count += 1
end) end)
@ -294,7 +294,7 @@ TEST("source()", wrap_root(function()
local src = source {} local src = source {}
local count = 0 local count = 0
watch(function() effect(function()
src() src()
count += 1 count += 1
end) end)
@ -311,7 +311,7 @@ TEST("source()", wrap_root(function()
local src = source(a) local src = source(a)
local count = 0 local count = 0
watch(function() effect(function()
src() src()
count += 1 count += 1
end) end)
@ -330,7 +330,7 @@ TEST("derive()", wrap_root(function()
local root = vide.root local root = vide.root
local source = vide.source local source = vide.source
local derive = vide.derive local derive = vide.derive
local watch = vide.watch local effect = vide.effect
local cleanup = vide.cleanup local cleanup = vide.cleanup
do CASE "derive new value on source change" do CASE "derive new value on source change"
@ -371,7 +371,7 @@ TEST("derive()", wrap_root(function()
local count = 0 local count = 0
watch(function() effect(function()
is_even() is_even()
count += 1 count += 1
end) end)
@ -399,7 +399,7 @@ TEST("derive()", wrap_root(function()
local count = 0 local count = 0
watch(function() c() count += 1 end) effect(function() c() count += 1 end)
b(true) b(true)
CHECK(c() == "b") CHECK(c() == "b")
@ -455,16 +455,16 @@ TEST("derive()", wrap_root(function()
end end
end)) end))
TEST("watch()", wrap_root(function() TEST("effect()", wrap_root(function()
local source = vide.source local source = vide.source
local watch = vide.watch local effect = vide.effect
do CASE "rerun on source change" do CASE "rerun on source change"
local a = source(1) local a = source(1)
local b = source(1) local b = source(1)
local count = 0 local count = 0
watch(function() effect(function()
a() a()
b() b()
count += 1 count += 1
@ -481,7 +481,7 @@ end))
TEST("cleanup()", wrap_root(function() TEST("cleanup()", wrap_root(function()
local root = vide.root local root = vide.root
local source = vide.source local source = vide.source
local watch = vide.watch local effect = vide.effect
local cleanup = vide.cleanup local cleanup = vide.cleanup
do CASE "root cleanup" do CASE "root cleanup"
@ -503,23 +503,23 @@ TEST("cleanup()", wrap_root(function()
do CASE "cleanup on rerun" do CASE "cleanup on rerun"
local state = source(1) local state = source(1)
local watched = 0 local effected = 0
local cleaned = 0 local cleaned = 0
watch(function() effect(function()
state() state()
watched += 1 effected += 1
cleanup(function() cleanup(function()
cleaned += 1 cleaned += 1
end) end)
end) end)
CHECK(watched == 1) CHECK(effected == 1)
CHECK(cleaned == 0) CHECK(cleaned == 0)
state(2) state(2)
CHECK(watched == 2) CHECK(effected == 2)
CHECK(cleaned == 1) CHECK(cleaned == 1)
end end
@ -528,7 +528,7 @@ TEST("cleanup()", wrap_root(function()
local queue = {} local queue = {}
watch(function() effect(function()
state() state()
cleanup(function() table.insert(queue, 1) end) cleanup(function() table.insert(queue, 1) end)
cleanup(function() table.insert(queue, 2) end) cleanup(function() table.insert(queue, 2) end)
@ -1001,7 +1001,7 @@ TEST("spring()", wrap_root(function()
local create = vide.create local create = vide.create
local source = vide.source local source = vide.source
local spring = vide.spring local spring = vide.spring
local watch = vide.watch local effect = vide.effect
do CASE "update source (on next step)" do CASE "update source (on next step)"
local value = source(10) local value = source(10)
@ -1082,7 +1082,7 @@ TEST("spring()", wrap_root(function()
CHECK(output() == input()) -- check spring is at target CHECK(output() == input()) -- check spring is at target
local count = -1 local count = -1
watch(function() effect(function()
output() output()
count += 1 count += 1
end) end)
@ -1102,7 +1102,7 @@ TEST("untrack()", wrap_root(function()
local root = vide.root local root = vide.root
local source = vide.source local source = vide.source
local derive = vide.derive local derive = vide.derive
local watch = vide.watch local effect = vide.effect
local cleanup = vide.cleanup local cleanup = vide.cleanup
local untrack = vide.untrack local untrack = vide.untrack
@ -1112,7 +1112,7 @@ TEST("untrack()", wrap_root(function()
local count = 0 local count = 0
watch(function() effect(function()
count += 1 count += 1
untrack(a) untrack(a)
b() b()
@ -1138,7 +1138,7 @@ TEST("untrack()", wrap_root(function()
local count = 0 local count = 0
watch(function() effect(function()
count += 1 count += 1
untrack(d) untrack(d)
c() c()
@ -1275,7 +1275,7 @@ end)
-- local create = vide.create -- local create = vide.create
-- local source = vide.source -- local source = vide.source
-- local derive = vide.derive -- local derive = vide.derive
-- local watch = vide.watch -- local effect = vide.effect
-- local indexes, values = vide.indexes, vide.values -- local indexes, values = vide.indexes, vide.values
-- local cleanup = vide.cleanup -- local cleanup = vide.cleanup
@ -1292,11 +1292,11 @@ end)
-- -- CHECK(not ok) -- -- CHECK(not ok)
-- -- end -- -- end
-- -- do CASE "error on watcher callback yield" -- -- do CASE "error on effecter callback yield"
-- -- local state = source(1) -- -- local state = source(1)
-- -- local ok = pcall(function() -- -- local ok = pcall(function()
-- -- local _derived = watch(function() -- -- local _derived = effect(function()
-- -- coroutine.yield() -- -- coroutine.yield()
-- -- state() -- -- state()
-- -- end) -- -- end)
@ -1319,11 +1319,11 @@ end)
-- CHECK(runcount == 4) -- CHECK(runcount == 4)
-- end -- end
-- do CASE "run watcher callback twice" -- do CASE "run effecter callback twice"
-- local state = source(1) -- local state = source(1)
-- local runcount = 0 -- local runcount = 0
-- watch(function() -- effect(function()
-- runcount += 1 -- runcount += 1
-- state() -- state()
-- end) -- end)