mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
Add context()
This commit is contained in:
parent
83db00a073
commit
8142acd1c1
8 changed files with 303 additions and 1 deletions
|
|
@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `context()`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Error stack traces being lost.
|
- Error stack traces being lost.
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,11 @@ Creates a new source with the given value.
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
function source<T>(value: T): (T?) -> T
|
function source<T>(value: T): Source<T>
|
||||||
|
|
||||||
|
type Source<T> =
|
||||||
|
() -> T -- get
|
||||||
|
& (T) -> () -- set
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
|
||||||
|
|
@ -90,4 +90,45 @@ trigger effects until after the function finishes running.
|
||||||
only cause the effect to run once after the batch call ends instead of after
|
only cause the effect to run once after the batch call ends instead of after
|
||||||
each time a source is updated.
|
each time a source is updated.
|
||||||
|
|
||||||
|
## context()
|
||||||
|
|
||||||
|
Creates a new context.
|
||||||
|
|
||||||
|
- **Type**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function context<T>(default: T): Context<T>
|
||||||
|
|
||||||
|
type Context<T> =
|
||||||
|
() -> T -- get
|
||||||
|
& (T, () -> ()) -> () -- set
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Details**
|
||||||
|
|
||||||
|
Calling `context()` returns a new context function.
|
||||||
|
Call this function with no arguments to get the context value.
|
||||||
|
Call this function with a value and a callback to set a new context with the
|
||||||
|
given value.
|
||||||
|
|
||||||
|
- **Example**
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local theme = context()
|
||||||
|
|
||||||
|
local function Button()
|
||||||
|
print(theme())
|
||||||
|
end
|
||||||
|
|
||||||
|
root(function()
|
||||||
|
theme("light", function()
|
||||||
|
Button() -- prints "light"
|
||||||
|
|
||||||
|
theme("dark", function()
|
||||||
|
Button() -- prints "dark"
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
75
src/context.luau
Normal file
75
src/context.luau
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
if not game then script = require "test/relative-string" end
|
||||||
|
|
||||||
|
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 push_scope = graph.push_scope
|
||||||
|
local pop_scope = graph.pop_scope
|
||||||
|
local set_context = graph.set_context
|
||||||
|
|
||||||
|
export type Context<T> = (() -> T) & ((T, () -> ()) -> ())
|
||||||
|
|
||||||
|
local nil_symbol = newproxy()
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
local function context<T>(...: T): Context<T>
|
||||||
|
count += 1
|
||||||
|
local id = count
|
||||||
|
|
||||||
|
local has_default = select("#", ...) > 0
|
||||||
|
local default_value = ...
|
||||||
|
|
||||||
|
return function(...)
|
||||||
|
local scope: Node<unknown>? | false = get_scope()
|
||||||
|
|
||||||
|
if select("#", ...) == 0 then -- get
|
||||||
|
while scope do
|
||||||
|
local ctx = scope.context
|
||||||
|
|
||||||
|
if not ctx then
|
||||||
|
scope = scope.owner
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local value = (ctx :: { unknown })[id]
|
||||||
|
|
||||||
|
if value == nil then
|
||||||
|
scope = scope.owner
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
return (if value ~= nil_symbol then value else nil) :: T
|
||||||
|
end
|
||||||
|
|
||||||
|
if has_default ~= nil then
|
||||||
|
return default_value
|
||||||
|
else
|
||||||
|
throw("attempt to get context when no context is set and no default context is set")
|
||||||
|
end
|
||||||
|
else -- set
|
||||||
|
if not scope then return throw("attempt to set context outside of a vide scope") end
|
||||||
|
|
||||||
|
local value, component = ...
|
||||||
|
|
||||||
|
local new_scope = create_node(scope, false, false)
|
||||||
|
set_context(new_scope, id, if value == nil then nil_symbol else value)
|
||||||
|
|
||||||
|
push_scope(new_scope)
|
||||||
|
|
||||||
|
local function efn(err: string) return debug.traceback(err, 3) end
|
||||||
|
local ok, result = xpcall(component, efn)
|
||||||
|
|
||||||
|
pop_scope()
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
throw(`error while running context:\n\n{result}`)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
@ -13,6 +13,8 @@ export type Node<T> = {
|
||||||
effect: ((T) -> T) | false,
|
effect: ((T) -> T) | false,
|
||||||
cleanups: { () -> () } | false,
|
cleanups: { () -> () } | false,
|
||||||
|
|
||||||
|
context: { [number]: unknown } | false,
|
||||||
|
|
||||||
owned: { Node<T> } | false,
|
owned: { Node<T> } | false,
|
||||||
owner: Node<T> | false,
|
owner: Node<T> | false,
|
||||||
|
|
||||||
|
|
@ -241,6 +243,8 @@ local function create_node<T>(owner: false | Node<any>, effect: false | (T) -> T
|
||||||
effect = effect,
|
effect = effect,
|
||||||
cleanups = false,
|
cleanups = false,
|
||||||
|
|
||||||
|
context = false,
|
||||||
|
|
||||||
owner = owner,
|
owner = owner,
|
||||||
owned = false,
|
owned = false,
|
||||||
|
|
||||||
|
|
@ -266,6 +270,14 @@ local function get_children<T>(node: Node<T>): { Node<unknown> }
|
||||||
return { unpack(node) } :: { Node<any> }
|
return { unpack(node) } :: { Node<any> }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function set_context<T>(node: Node<T>, key: number, value: unknown)
|
||||||
|
if node.context then
|
||||||
|
node.context[key] = value
|
||||||
|
else
|
||||||
|
node.context = { [key] = value }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return table.freeze {
|
return table.freeze {
|
||||||
push_scope = push_scope,
|
push_scope = push_scope,
|
||||||
pop_scope = pop_scope,
|
pop_scope = pop_scope,
|
||||||
|
|
@ -283,5 +295,6 @@ return table.freeze {
|
||||||
get_children = get_children,
|
get_children = get_children,
|
||||||
flush_update_queue = flush_update_queue,
|
flush_update_queue = flush_update_queue,
|
||||||
get_update_queue_length = get_update_queue_length,
|
get_update_queue_length = get_update_queue_length,
|
||||||
|
set_context = set_context,
|
||||||
scopes = scopes
|
scopes = scopes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ local cleanup = require(script.cleanup)
|
||||||
local untrack = require(script.untrack)
|
local untrack = require(script.untrack)
|
||||||
local read = require(script.read)
|
local read = require(script.read)
|
||||||
local batch = require(script.batch)
|
local batch = require(script.batch)
|
||||||
|
local context = require(script.context)
|
||||||
local switch = require(script.switch)
|
local switch = require(script.switch)
|
||||||
local show = require(script.show)
|
local show = require(script.show)
|
||||||
local indexes, values = require(script.maps)()
|
local indexes, values = require(script.maps)()
|
||||||
|
|
@ -26,6 +27,9 @@ local throw = require(script.throw)
|
||||||
local flags = require(script.flags)
|
local flags = require(script.flags)
|
||||||
|
|
||||||
export type Source<T> = source.Source<T>
|
export type Source<T> = source.Source<T>
|
||||||
|
export type source<T> = Source<T>
|
||||||
|
export type Context<T> = context.Context<T>
|
||||||
|
export type context<T> = Context<T>
|
||||||
|
|
||||||
local function step(dt: number)
|
local function step(dt: number)
|
||||||
if game then
|
if game then
|
||||||
|
|
@ -63,6 +67,7 @@ local vide = {
|
||||||
untrack = untrack,
|
untrack = untrack,
|
||||||
read = read,
|
read = read,
|
||||||
batch = batch,
|
batch = batch,
|
||||||
|
context = context,
|
||||||
|
|
||||||
-- animations
|
-- animations
|
||||||
spring = spring,
|
spring = spring,
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,14 @@ local BENCH, START = testkit.benchmark()
|
||||||
local vide = require "src/init"
|
local vide = require "src/init"
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
local derive = vide.derive
|
local derive = vide.derive
|
||||||
|
local effect = vide.effect
|
||||||
local indexes = vide.indexes
|
local indexes = vide.indexes
|
||||||
local values = vide.values
|
local values = vide.values
|
||||||
local batch = vide.batch
|
local batch = vide.batch
|
||||||
local cleanup = vide.cleanup
|
local cleanup = vide.cleanup
|
||||||
|
local untrack = vide.untrack
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
|
local context = vide.context
|
||||||
|
|
||||||
assert(not vide.strict)
|
assert(not vide.strict)
|
||||||
|
|
||||||
|
|
@ -446,6 +449,61 @@ ROOT_BENCH("values() all remove", function()
|
||||||
src(data)
|
src(data)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
TITLE "context()"
|
||||||
|
|
||||||
|
ROOT_BENCH("set context", function()
|
||||||
|
local ctx = context()
|
||||||
|
|
||||||
|
for i = 1, START(N) do
|
||||||
|
ctx(i, function() end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
ROOT_BENCH("get context (depth=1)", function()
|
||||||
|
local ctx = context()
|
||||||
|
|
||||||
|
local function run()
|
||||||
|
for i = 1, START(N) do
|
||||||
|
ctx()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ctx(1, function()
|
||||||
|
run()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local depth = 10
|
||||||
|
ROOT_BENCH(`get context (depth={depth})`, function()
|
||||||
|
|
||||||
|
local ctx = context()
|
||||||
|
|
||||||
|
local function run()
|
||||||
|
for i = 1, START(N) do
|
||||||
|
ctx()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function nest_effect(fn)
|
||||||
|
untrack(function()
|
||||||
|
effect(fn)
|
||||||
|
return nil
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local f = run
|
||||||
|
for i = 1, depth - 1 do
|
||||||
|
local f_inner = f
|
||||||
|
f = function()
|
||||||
|
nest_effect(f_inner)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ctx(1, function()
|
||||||
|
f()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
N *= 1024
|
N *= 1024
|
||||||
|
|
||||||
TITLE "aggregate"
|
TITLE "aggregate"
|
||||||
|
|
|
||||||
102
test/tests.luau
102
test/tests.luau
|
|
@ -2155,6 +2155,108 @@ TEST("read()", wrap_root(function()
|
||||||
end
|
end
|
||||||
end))
|
end))
|
||||||
|
|
||||||
|
TEST("context()", function()
|
||||||
|
local root = vide.root
|
||||||
|
local context = vide.context
|
||||||
|
local effect = vide.effect
|
||||||
|
local untrack = vide.untrack
|
||||||
|
local show = vide.show
|
||||||
|
|
||||||
|
do CASE "set context"
|
||||||
|
local ctx = context()
|
||||||
|
|
||||||
|
root(function()
|
||||||
|
ctx(1, function()
|
||||||
|
CHECK(ctx() == 1)
|
||||||
|
|
||||||
|
effect(function()
|
||||||
|
CHECK(ctx() == 1)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "set context outside of scope"
|
||||||
|
local ctx = context()
|
||||||
|
|
||||||
|
local ok = pcall(function()
|
||||||
|
ctx(1, function() end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
CHECK(not ok)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "get default context"
|
||||||
|
local ctx = context(1)
|
||||||
|
|
||||||
|
CHECK(ctx() == 1)
|
||||||
|
|
||||||
|
root(function()
|
||||||
|
ctx(2, function()
|
||||||
|
CHECK(ctx() == 2)
|
||||||
|
end)
|
||||||
|
|
||||||
|
CHECK(ctx() == 1)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "context cascade"
|
||||||
|
local ctx = context(1)
|
||||||
|
local ctx2 = context()
|
||||||
|
|
||||||
|
root(function()
|
||||||
|
ctx(2, function()
|
||||||
|
ctx2(true, function()
|
||||||
|
show(function() return true end, function()
|
||||||
|
ctx(3, function()
|
||||||
|
effect(function()
|
||||||
|
CHECK(ctx() == 3)
|
||||||
|
untrack(function()
|
||||||
|
effect(function()
|
||||||
|
CHECK(ctx() == 3)
|
||||||
|
end)
|
||||||
|
CHECK(ctx2() == true)
|
||||||
|
return {}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
CHECK(ctx() == 3)
|
||||||
|
end)
|
||||||
|
CHECK(ctx() == 2)
|
||||||
|
return {}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
CHECK(ctx() == 2)
|
||||||
|
end)
|
||||||
|
|
||||||
|
CHECK(ctx() == 1)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "nil context"
|
||||||
|
local ctx = context(nil)
|
||||||
|
|
||||||
|
root(function()
|
||||||
|
CHECK(ctx() == nil)
|
||||||
|
ctx(nil, function()
|
||||||
|
CHECK(ctx() == nil)
|
||||||
|
ctx(true :: any, function()
|
||||||
|
CHECK(ctx() == true)
|
||||||
|
ctx(nil, function()
|
||||||
|
effect(function()
|
||||||
|
untrack(function()
|
||||||
|
effect(function()
|
||||||
|
CHECK(ctx() == nil)
|
||||||
|
end)
|
||||||
|
return {}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
TEST("nested effects cases", function()
|
TEST("nested effects cases", function()
|
||||||
local vide = require "src/init"
|
local vide = require "src/init"
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue