This commit is contained in:
Aaron Smith 2023-09-11 18:20:21 +01:00
parent 52ea2e4690
commit 5b61a9df10
13 changed files with 57 additions and 39 deletions

View file

@ -2,6 +2,26 @@
<br/>
## root()
Creates and runs a function in a new reactive scope.
- **Type**
```lua
function root<T>(fn: () -> T): (T, () -> ())
```
- **Details**
Creates a new root reactive scope, where creation and derivations of sources
can be tracked and properly disposed of.
Returns the result of the given function.
Also returns a function to destroy the root, which will run any cleanups
and allow derived sources created to garbage collect.
## source()
Creates a new source with the given value.
@ -14,10 +34,10 @@ Creates a new source with the given value.
- **Details**
Calling the returned source with no arguments will return its stored value,
calling with arguments will set a new value.
Calling the returned source with no argument will return its stored value,
calling with an argument will set a new value.
Reading from the source from within any reactive scope will cause changes
Reading from the source from within a reactive scope will cause changes
to that source to be tracked and anything depending on it to update.
- **Example**

View file

@ -2,7 +2,7 @@
## cleanup()
Runs a callback anytime a function scope is re-ran.
Runs a callback anytime a reactive scope is re-ran.
- **Type**
@ -10,19 +10,6 @@ Runs a callback anytime a function scope is re-ran.
function cleanup(callback: () -> ())
```
- **Details**
The primary purpose of this function is to provide a means of cleaning up
side effects caused by source updates and `watch()` updates.
The stack is inspected to find the function that calls `cleanup()`. The
callback passed is called anytime the caller is re-ran, and when the caller
finally garbage collects.
::: warning
Only one `cleanup()` call is allowed per function scope.
:::
- **Example**
```lua
@ -53,7 +40,7 @@ Runs a callback anytime a function scope is re-ran.
## untrack()
Gets the value of a source without reactively tracking it.
Runs a given function where any sources read will not track its reactive scope.
- **Type**

View file

@ -3,7 +3,7 @@
This is a brief tutorial designed to give you a quick run through the usage of
Vide.
Vide is largely inspired by other UI libraries such as Solid and Fusion.
Vide is heavily inspired by [Solid](https://www.solidjs.com/).
## Why Vide?
@ -21,5 +21,6 @@ Some of the main focuses behind Vide's design choices:
- Being completely typecheckable.
- Flexibility, particularly with integrating other libraries and allowing users
to use their own patterns.
- A powerful reactive system that does not interfere with the lifetime of
instances.
- Independence from instance lifetimes.
- A powerful reactive system that can surgically update properties as a result
of state changes.

View file

@ -138,7 +138,7 @@ local function apply<T>(instance: T & Instance, properties: { [unknown]: unknown
-- finally set parent if any
if parent then
if type(parent) == "function" then
bind.parent(instance, parent :: () -> ())
bind.parent(instance, parent :: () -> Instance)
else
instance.Parent = parent :: Instance
end

View file

@ -51,6 +51,7 @@ function create_binding<T>(updater: (T) -> T, binding_data: T)
binding.effect = updater
local owner = get_scope()
if not owner then throw("cannot bind property in non-reactive scope") end
assert(owner)
set_owner(binding, owner)

View file

@ -1,15 +1,16 @@
if not game then script = require "test/relative-string" end
local flags = require(script.Parent.flags)
local throw = require(script.Parent.throw)
local graph = require(script.Parent.graph)
local get_scope = graph.get_scope
local add_cleanup = graph.add_cleanup
local function cleanup(fn: () -> ())
local node = get_scope()
if node == nil then throw("cannot call cleanup() in a non-reactive scope") end
add_cleanup(node, fn)
local scope = get_scope()
if not scope then throw("cannot cleanup in a non-reactive scope") end
assert(scope)
add_cleanup(scope, fn)
end
return cleanup

View file

@ -1,5 +1,6 @@
if not game then script = require "test/relative-string" end
local throw = require(script.Parent.throw)
local graph = require(script.Parent.graph)
local create_node = graph.create_node
local set_owner = graph.set_owner
@ -8,8 +9,10 @@ local get_scope = graph.get_scope
local open_scope = graph.open_scope
local close_scope = graph.close_scope
local function derive<T>(fn: () -> T): () -> T
local owner = get_scope()
if not owner then throw("cannot derive in non-reactive scope") end
assert(owner)
local node = create_node((false :: any) :: T)

View file

@ -5,7 +5,7 @@ if not game then script = require "test/relative-string" end
local throw = require(script.Parent.throw)
local flags = require(script.Parent.flags)
local graph = require(script.Parent.graph)
type Scope = graph.Scope
type Node<T> = graph.Node<T>
type StartNode<T> = graph.StartNode<T>
local create_node = graph.create_node
local create_start_node = graph.create_start_node
@ -28,9 +28,12 @@ local function check_primitives(t: {})
end
end
-- todo: verify destruction of subscopes when owner scope is destroyed
-- todo: optimize output array
local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI, K) -> VO): () -> { VO }
local owner = get_scope()
if not owner then throw("cannot derive in non-reactive scope") end
assert(owner)
local input_cache = {} :: Map<K, VI>
@ -38,7 +41,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
local input_nodes = {} :: Map<K, StartNode<VI>>
local remove_queue = {} :: { K }
local scopes = {} :: Map<K, Scope>
local scopes = {} :: Map<K, Node<unknown>>
local function update_children(data)
-- queue removed values
@ -69,7 +72,7 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
if cv ~= v then
if cv == nil then
local scope = create_node(false)
scopes[i] = scope
scopes[i] = scope :: Node<any>
set_owner(scope, owner)
open_scope(scope)
@ -122,6 +125,7 @@ end
-- todo: optimize output array
local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () -> K) -> VO): () -> { VO }
local owner = get_scope()
if not owner then throw("cannot derive in non-reactive scope") end
assert(owner)
local cur_input_cache_up = {} :: Map<VI, K>
@ -130,7 +134,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
local output_cache = {} :: Map<VI, VO>
local input_nodes = {} :: Map<VI, StartNode<K>>
local scopes = {} :: Map<VI, Scope>
local scopes = {} :: Map<VI, Node<unknown>>
local function update_children(data: Map<K, VI>)
local cur_input_cache, new_input_cache = cur_input_cache_up, new_input_cache_up
@ -155,7 +159,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
if cv == nil then
local scope = create_node(false)
scopes[v] = scope
scopes[v] = scope :: Node<any>
set_owner(scope, owner)
open_scope(scope)

View file

@ -14,7 +14,6 @@ local destroy = graph.destroy
local refs = {}
local function root<T>(fn: () -> T): (T, () -> ())
--assert(not get_scope())
local node = create_node(false)
open_scope(node)

View file

@ -151,6 +151,7 @@ setmetatable(springs, { __mode = "v" })
local function spring<T>(source: () -> T, period: number?, damping_ratio: number?): () -> T
local owner = get_scope()
if not owner then throw("cannot derive in non-reactive scope") end
assert(owner)
local updater = create_node(false)
@ -199,6 +200,7 @@ local function spring<T>(source: () -> T, period: number?, damping_ratio: number
data.x1_123, data.x1_456 = type_to_vec6[typeof(v)](v)
data.source_value = v
springs[data] = output -- todo: investigate why insertion is not O(1) at ~20k springs
return false
end
return function()

View file

@ -1,15 +1,13 @@
if not game then script = require "test/relative-string" end
local create = require(script.Parent.create)
local throw = require(script.Parent.throw)
local graph = require(script.Parent.graph)
type Node<T> = graph.Node<T>
local create_node = graph.create_node
local get_scope = graph.get_scope
local open_scope = graph.open_scope
local close_scope = graph.close_scope
local function untrack<T>(source: () -> T): T
local scope = get_scope()
if not scope then throw("cannot untrack in non-reactive scope") end
assert(scope)
local effect = scope.effect

View file

@ -1,5 +1,6 @@
if not game then script = require "test/relative-string" end
local throw = require(script.Parent.throw)
local graph = require(script.Parent.graph)
local create_node = graph.create_node
local get_scope = graph.get_scope
@ -9,9 +10,10 @@ local set_owner = graph.set_owner
local function watch<T>(effect: (T) -> T, initial_value: T)
local owner = get_scope()
if not owner then throw("cannot watch in non-reactive scope") end
assert(owner)
local node = create_node(false)
local node = create_node(initial_value)
node.effect = effect
set_owner(node, owner)

View file

@ -1,5 +1,5 @@
local testkit = require("test/testkit")
local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
local TEST, CASE, CHECK, FINISH = testkit.test()
local mock = require "test/mock"
local Instance, Signal = mock.Instance, mock.Signal
@ -29,7 +29,7 @@ local function wrap_root(fn: () -> ())
end
end
local NIL = NIL
local NIL = nil
TEST("graph", function()
local create_node = graph.create_node