diff --git a/docs/api/reactivity-core.md b/docs/api/reactivity-core.md
index 7f9fe28..02390b7 100644
--- a/docs/api/reactivity-core.md
+++ b/docs/api/reactivity-core.md
@@ -2,6 +2,26 @@
+## root()
+
+Creates and runs a function in a new reactive scope.
+
+- **Type**
+
+ ```lua
+ function root(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**
diff --git a/docs/api/reactivity-utility.md b/docs/api/reactivity-utility.md
index 4597364..82b9fd5 100644
--- a/docs/api/reactivity-utility.md
+++ b/docs/api/reactivity-utility.md
@@ -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**
diff --git a/docs/tut/crash-course/1-introduction.md b/docs/tut/crash-course/1-introduction.md
index 0197384..7376579 100644
--- a/docs/tut/crash-course/1-introduction.md
+++ b/docs/tut/crash-course/1-introduction.md
@@ -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.
diff --git a/src/apply.luau b/src/apply.luau
index 8ef87ed..960ff8b 100644
--- a/src/apply.luau
+++ b/src/apply.luau
@@ -138,7 +138,7 @@ local function apply(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
diff --git a/src/bind.luau b/src/bind.luau
index fe1605d..914ba45 100644
--- a/src/bind.luau
+++ b/src/bind.luau
@@ -51,6 +51,7 @@ function create_binding(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)
diff --git a/src/cleanup.luau b/src/cleanup.luau
index 3aec998..2d8fc73 100644
--- a/src/cleanup.luau
+++ b/src/cleanup.luau
@@ -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
diff --git a/src/derive.luau b/src/derive.luau
index 3b7972f..62a7096 100644
--- a/src/derive.luau
+++ b/src/derive.luau
@@ -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(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)
diff --git a/src/maps.luau b/src/maps.luau
index b8eedfb..ffbfa7e 100644
--- a/src/maps.luau
+++ b/src/maps.luau
@@ -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 = graph.Node
type StartNode = graph.StartNode
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(input: () -> Map, 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
@@ -38,7 +41,7 @@ local function indexes(input: () -> Map, transform: (() -> VI,
local input_nodes = {} :: Map>
local remove_queue = {} :: { K }
- local scopes = {} :: Map
+ local scopes = {} :: Map>
local function update_children(data)
-- queue removed values
@@ -69,7 +72,7 @@ local function indexes(input: () -> Map, transform: (() -> VI,
if cv ~= v then
if cv == nil then
local scope = create_node(false)
- scopes[i] = scope
+ scopes[i] = scope :: Node
set_owner(scope, owner)
open_scope(scope)
@@ -122,6 +125,7 @@ end
-- todo: optimize output array
local function values(input: () -> Map, 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
@@ -130,7 +134,7 @@ local function values(input: () -> Map, transform: (VI, () ->
local output_cache = {} :: Map
local input_nodes = {} :: Map>
- local scopes = {} :: Map
+ local scopes = {} :: Map>
local function update_children(data: Map)
local cur_input_cache, new_input_cache = cur_input_cache_up, new_input_cache_up
@@ -155,7 +159,7 @@ local function values(input: () -> Map, transform: (VI, () ->
if cv == nil then
local scope = create_node(false)
- scopes[v] = scope
+ scopes[v] = scope :: Node
set_owner(scope, owner)
open_scope(scope)
diff --git a/src/root.luau b/src/root.luau
index d795d4d..b211518 100644
--- a/src/root.luau
+++ b/src/root.luau
@@ -14,7 +14,6 @@ local destroy = graph.destroy
local refs = {}
local function root(fn: () -> T): (T, () -> ())
- --assert(not get_scope())
local node = create_node(false)
open_scope(node)
diff --git a/src/spring.luau b/src/spring.luau
index 9b4e35f..ee76e10 100644
--- a/src/spring.luau
+++ b/src/spring.luau
@@ -151,6 +151,7 @@ setmetatable(springs, { __mode = "v" })
local function spring(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(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()
diff --git a/src/untrack.luau b/src/untrack.luau
index 0dbde8a..abdfb78 100644
--- a/src/untrack.luau
+++ b/src/untrack.luau
@@ -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 = graph.Node
-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(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
diff --git a/src/watch.luau b/src/watch.luau
index 9213631..c5ee5df 100644
--- a/src/watch.luau
+++ b/src/watch.luau
@@ -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(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)
diff --git a/test/tests.luau b/test/tests.luau
index 574bdc6..9c7fbe9 100644
--- a/test/tests.luau
+++ b/test/tests.luau
@@ -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