mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
This commit is contained in:
parent
96e577a626
commit
bf1b1978d9
8 changed files with 279 additions and 226 deletions
|
|
@ -1,10 +1,9 @@
|
||||||
# Vide Crash Course
|
# Vide Crash Course
|
||||||
|
|
||||||
Hello! This is a brief tutorial designed to give you a quick runthrough of the usage of Vide.
|
This is a brief tutorial designed to give you a quick run through the usage of
|
||||||
|
Vide.
|
||||||
|
|
||||||
Vide is inspired by the popular libraries Vue and Fusion.
|
Vide is largely inspired by Solid.
|
||||||
|
|
||||||
- Note that this tutorial assumes that you are familiar with Luau and the Roblox UI system.
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
|
@ -12,21 +11,22 @@ Vide is inspired by the popular libraries Vue and Fusion.
|
||||||
|
|
||||||
In Vide, it is intended to create all UI instances through code.
|
In Vide, it is intended to create all UI instances through code.
|
||||||
|
|
||||||
Instances are created using [`vide.create`](../api/creation#create).
|
Instances are created using [`vide.create()`](../api/creation#create).
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local vide = require(...)
|
local vide = require(vide)
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
```
|
```
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local frame = create("Frame") {
|
local frame = create "Frame" {
|
||||||
Name = "Background",
|
Name = "Background",
|
||||||
Position = UDim2.fromScale(0.5, 0.5)
|
Position = UDim2.fromScale(0.5, 0.5)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The function returns a constructor for a given class which then takes a table of properties to assign to create a new instance for that class.
|
`create()` returns a constructor for a given class which then takes a table of
|
||||||
|
properties to assign when creating a new instance for that class.
|
||||||
|
|
||||||
Sometimes you want to do more than setting properties, such as setting children or connecting to events.
|
Sometimes you want to do more than setting properties, such as setting children or connecting to events.
|
||||||
Vide uses special keys called *symbols* which provide unique functionality like the above mentioned.
|
Vide uses special keys called *symbols* which provide unique functionality like the above mentioned.
|
||||||
|
|
@ -250,33 +250,33 @@ Often, you need components that maintain their own internal state, such as a tog
|
||||||
Below you can see how a simple counter component can be implemented.
|
Below you can see how a simple counter component can be implemented.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local function Counter(args)
|
local function Counter(props: {
|
||||||
|
Layout: Layout
|
||||||
|
})
|
||||||
-- create internal state unique to each component instance
|
-- create internal state unique to each component instance
|
||||||
local count = wrap(0)
|
local count = source(0)
|
||||||
|
|
||||||
return create("TextButton") {
|
return create "TextButton" {
|
||||||
Name = "Counter",
|
Text = function()
|
||||||
|
return "Count: " .. count
|
||||||
Text = "Count: " .. count
|
|
||||||
|
|
||||||
[Event.Activated] = function()
|
|
||||||
count.value += 1
|
|
||||||
end
|
end
|
||||||
|
|
||||||
[Layout] = args[Layout]
|
Activated = function()
|
||||||
|
count(count() + 1)
|
||||||
|
end,
|
||||||
|
|
||||||
|
Layout = props.Layout
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
create "ScreenGui" {
|
create "ScreenGui" {
|
||||||
Parent = game.StarterGui,
|
Parent = game.StarterGui,
|
||||||
|
|
||||||
[Children] = {
|
Counter {
|
||||||
Counter {
|
Layout = {
|
||||||
[Layout] = {
|
AnchorPoint = Vector2.new(0.5, 0),
|
||||||
AnchorPoint = Vector2.new(0.5, 0),
|
Position = UDim2.fromScale(0.5, 0),
|
||||||
Position = UDim2.fromScale(0.5, 0),
|
Size = UDim2.fromScale(0.3, 0.1)
|
||||||
Size = UDim2.fromScale(0.3, 0.1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
src/bind.lua
13
src/bind.lua
|
|
@ -47,7 +47,7 @@ local function traceback() -- ensures trace begins outside of any vide library f
|
||||||
return debug.traceback("", s)
|
return debug.traceback("", s)
|
||||||
end
|
end
|
||||||
|
|
||||||
function setup(instance: Instance, deriver: () -> unknown, setter: (Instance) -> ())
|
function setup(instance: Instance, setter: (Instance) -> ())
|
||||||
if flags.strict then
|
if flags.strict then
|
||||||
local fn = setter
|
local fn = setter
|
||||||
local trace = traceback()
|
local trace = traceback()
|
||||||
|
|
@ -70,7 +70,8 @@ function setup(instance: Instance, deriver: () -> unknown, setter: (Instance) ->
|
||||||
weak[key] = instance
|
weak[key] = instance
|
||||||
|
|
||||||
local function ref()
|
local function ref()
|
||||||
local _ = nodes -- prevent gc of state while instance exists
|
--todo:local _ = nodes -- prevent gc of state while instance exists
|
||||||
|
local _ = setter
|
||||||
local instance = weak[key] :: Instance
|
local instance = weak[key] :: Instance
|
||||||
hold[key] = instance.Parent and instance or nil -- prevent gc of instance while parented
|
hold[key] = instance.Parent and instance or nil -- prevent gc of instance while parented
|
||||||
end
|
end
|
||||||
|
|
@ -79,8 +80,10 @@ function setup(instance: Instance, deriver: () -> unknown, setter: (Instance) ->
|
||||||
instance:GetPropertyChangedSignal("Parent"):Connect(ref)
|
instance:GetPropertyChangedSignal("Parent"):Connect(ref)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- todo: move `fn` as arg?
|
||||||
|
|
||||||
local function bind_property(instance: Instance, property: string, fn: () -> unknown)
|
local function bind_property(instance: Instance, property: string, fn: () -> unknown)
|
||||||
setup(instance, fn, function(instance_weak: any)
|
setup(instance, function(instance_weak: any)
|
||||||
instance_weak[property] = fn()
|
instance_weak[property] = fn()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
@ -89,7 +92,7 @@ local function bind_parent(instance: Instance, fn: () -> Instance?)
|
||||||
instance.Destroying:Connect(function()
|
instance.Destroying:Connect(function()
|
||||||
instance= nil :: any -- allow gc when destroyed
|
instance= nil :: any -- allow gc when destroyed
|
||||||
end)
|
end)
|
||||||
setup(instance, fn, function(instance)
|
setup(instance, function(instance)
|
||||||
local _ = instance -- state will strongly reference instance when parent is bound
|
local _ = instance -- state will strongly reference instance when parent is bound
|
||||||
instance.Parent = fn()
|
instance.Parent = fn()
|
||||||
end)
|
end)
|
||||||
|
|
@ -99,7 +102,7 @@ local function bind_children(parent: Instance, fn: () -> { Instance })
|
||||||
local currentChildrenSet: { [Instance]: true } = {} -- cache of all children parented before update
|
local currentChildrenSet: { [Instance]: true } = {} -- cache of all children parented before update
|
||||||
local newChildrenSet: { [Instance]: true } = {} -- cache of all children parented after update
|
local newChildrenSet: { [Instance]: true } = {} -- cache of all children parented after update
|
||||||
|
|
||||||
setup(parent, fn, function(parent_weak)
|
setup(parent, function(parent_weak)
|
||||||
local newChildren = fn() -- all (and only) children that should be parented after this update
|
local newChildren = fn() -- all (and only) children that should be parented after this update
|
||||||
if newChildren and type(newChildren) ~= "table" then
|
if newChildren and type(newChildren) ~= "table" then
|
||||||
throw(`Cannot parent instance of type { type(newChildren) } `)
|
throw(`Cannot parent instance of type { type(newChildren) } `)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
if not game then script = (require :: any) "test/wrap-require" end
|
if not game then script = (require :: any) "test/wrap-require" end
|
||||||
|
|
||||||
local create = require(script.create)
|
local create = require(script.create)
|
||||||
local wrap = require(script.wrap)
|
local source = require(script.source)
|
||||||
local watch = require(script.watch)
|
local watch = require(script.watch)
|
||||||
local derive = require(script.derive)
|
local derive = require(script.derive)
|
||||||
local map = require(script.map)
|
local map = require(script.map)
|
||||||
|
|
@ -14,18 +14,17 @@ local map = require(script.map)
|
||||||
-- local Changed = require(script.Change)
|
-- local Changed = require(script.Change)
|
||||||
-- local Created = require(script.Created)
|
-- local Created = require(script.Created)
|
||||||
|
|
||||||
local spring, updateSprings = require(script.spring)()
|
local spring, update_springs = require(script.spring)()
|
||||||
|
|
||||||
local flags = require(script.flags)
|
local flags = require(script.flags)
|
||||||
|
|
||||||
type Map<K, V> = { [K]: V }
|
type Map<K, V> = { [K]: V }
|
||||||
type Unwrapper = <T>(T) -> T
|
|
||||||
type Setter<T> = ( (new: T, force: true?) -> T ) & ( (update: (old: T) -> T, force: true?) -> T )
|
type Setter<T> = ( (new: T, force: true?) -> T ) & ( (update: (old: T) -> T, force: true?) -> T )
|
||||||
|
|
||||||
local vide = {
|
local vide = {
|
||||||
-- core
|
-- core
|
||||||
create = create,
|
create = create,
|
||||||
wrap = wrap,
|
source = source,
|
||||||
derive = derive,
|
derive = derive,
|
||||||
map = map,
|
map = map,
|
||||||
watch = watch,
|
watch = watch,
|
||||||
|
|
@ -45,7 +44,7 @@ local vide = {
|
||||||
|
|
||||||
-- test
|
-- test
|
||||||
step = function(dt: number)
|
step = function(dt: number)
|
||||||
updateSprings(dt)
|
update_springs(dt)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ local set = graph.set
|
||||||
|
|
||||||
type State<T> = (() -> T) & ((T) -> T)
|
type State<T> = (() -> T) & ((T) -> T)
|
||||||
|
|
||||||
local function wrap<T>(value: T | () -> T): State<T>
|
local function source<T>(value: T | () -> T): State<T>
|
||||||
if type(value) == "function" then value = value() end
|
if type(value) == "function" then value = value() end
|
||||||
|
|
||||||
local node = create(value :: T)
|
local node = create(value :: T)
|
||||||
|
|
@ -29,4 +29,4 @@ local function wrap<T>(value: T | () -> T): State<T>
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return wrap
|
return source
|
||||||
152
src/spring.lua
152
src/spring.lua
|
|
@ -29,18 +29,20 @@ local create = graph.create
|
||||||
local get = graph.get
|
local get = graph.get
|
||||||
local set = graph.set
|
local set = graph.set
|
||||||
|
|
||||||
|
type Node<T> = graph.Node<T>
|
||||||
|
|
||||||
type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3
|
type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3
|
||||||
|
|
||||||
type SpringData<T> = {
|
type SpringData<T> = {
|
||||||
Alpha: number;
|
alpha: number,
|
||||||
Duration: number;
|
duration: number,
|
||||||
Period: number;
|
period: number,
|
||||||
Damping: number;
|
damping_ratio: number,
|
||||||
Velocity: number;
|
velocity: number,
|
||||||
InitialVelocity: number;
|
initial_velocity: number,
|
||||||
Initial: T;
|
initial_position: T,
|
||||||
Target: T;
|
target_position: T,
|
||||||
Input: State<T>;
|
target: () -> T
|
||||||
}
|
}
|
||||||
|
|
||||||
type Lerp<T> = (initial: T, target: T, alpha: number) -> T
|
type Lerp<T> = (initial: T, target: T, alpha: number) -> T
|
||||||
|
|
@ -88,78 +90,76 @@ local lerpable: { [string]: Lerp<any> } = {
|
||||||
end :: Lerp<Vector3>,
|
end :: Lerp<Vector3>,
|
||||||
}
|
}
|
||||||
|
|
||||||
local activeSprings: { [State<any>]: SpringData<any> } = {}
|
local springs: { [SpringData<any>]: Node<any> } = {}
|
||||||
setmetatable(activeSprings, { __mode = "ks" })
|
setmetatable(springs, { __mode = "vs" })
|
||||||
|
|
||||||
local function spring<T>(input: MaybeState<T>, period: number, damping: number?): State<T>
|
local function spring<T>(target: () -> T, period: number, damping_ratio: number?): () -> T
|
||||||
local initial = unwrap(input) :: T
|
local initial_position = target()
|
||||||
local output = create(initial)
|
local node = create(initial_position)
|
||||||
|
|
||||||
local springData: SpringData<T> = {
|
local data: SpringData<T> = {
|
||||||
Alpha = 0,
|
alpha = 0,
|
||||||
Duration = 0,
|
duration = 0,
|
||||||
Period = period,
|
period = period,
|
||||||
Damping = damping or 1,
|
damping_ratio = damping_ratio or 1,
|
||||||
Velocity = 0,
|
velocity = 0,
|
||||||
InitialVelocity = 0,
|
initial_velocity = 0,
|
||||||
Initial = initial,
|
initial_position = initial_position,
|
||||||
Target = initial,
|
target_position = initial_position,
|
||||||
Input = input :: any,
|
target = target
|
||||||
LastInput = initial,
|
|
||||||
LastOutput = initial
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activeSprings[output] = springData
|
springs[data] = node
|
||||||
|
|
||||||
return output
|
return function()
|
||||||
end
|
return get(node)
|
||||||
|
|
||||||
local function updateSprings(dt: number)
|
|
||||||
for node, data in next, activeSprings do
|
|
||||||
local currentTarget = get(data.Input)
|
|
||||||
if currentTarget ~= data.Target then
|
|
||||||
data.Target = currentTarget
|
|
||||||
data.Initial = get(node)
|
|
||||||
data.Target = get(data.Input)
|
|
||||||
data.Alpha = 0
|
|
||||||
data.Duration = 0
|
|
||||||
data.InitialVelocity = data.Velocity
|
|
||||||
end
|
|
||||||
|
|
||||||
local initial: Animatable = data.Initial
|
|
||||||
local target: Animatable = data.Target
|
|
||||||
local targetType: string = typeof(target)
|
|
||||||
|
|
||||||
if targetType ~= typeof(initial) then
|
|
||||||
activeSprings[node] = nil
|
|
||||||
warn(string.format(
|
|
||||||
"Mismatched state value types, cancelling state update (initial value: %s, target value: %s)",
|
|
||||||
typeof(initial),
|
|
||||||
targetType
|
|
||||||
))
|
|
||||||
throw(`Cannot tween type { typeof(initial) } and { targetType }`)
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
local lerp: Lerp<Animatable> = lerpable[targetType]
|
|
||||||
|
|
||||||
if lerp == nil then
|
|
||||||
activeSprings[node] = nil
|
|
||||||
throw(`Cannot animate type { targetType }`)
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
local newTime = data.Duration + dt
|
|
||||||
local newAlpha = solve(data.Period, data.Damping, data.InitialVelocity, newTime)
|
|
||||||
|
|
||||||
data.Velocity = -(newAlpha - data.Alpha)/dt
|
|
||||||
data.Alpha = newAlpha
|
|
||||||
data.Duration = newTime
|
|
||||||
|
|
||||||
local value = lerp(initial, target, newAlpha)
|
|
||||||
|
|
||||||
set(node, value)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return function() return spring, updateSprings end
|
local function update_springs(dt: number)
|
||||||
|
for data, output in next, springs do
|
||||||
|
if data.target() ~= data.target_position then
|
||||||
|
data.target = data.target()
|
||||||
|
data.initial_position = get(output)
|
||||||
|
data.alpha = 0
|
||||||
|
data.duration = 0
|
||||||
|
data.initial_velocity = data.velocity
|
||||||
|
end
|
||||||
|
|
||||||
|
local initial_position: Animatable = data.initial_position
|
||||||
|
local target_position: Animatable = data.target_position
|
||||||
|
local target_type: string = typeof(target_position)
|
||||||
|
|
||||||
|
if target_type ~= typeof(initial_position) then
|
||||||
|
springs[data] = nil
|
||||||
|
warn(string.format(
|
||||||
|
"Mismatched state value types, cancelling state update (initial value: %s, target value: %s)",
|
||||||
|
typeof(initial_position),
|
||||||
|
target_type
|
||||||
|
))
|
||||||
|
throw(`Cannot tween type { typeof(initial_position) } and { target_type }`)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local lerp: Lerp<Animatable> = lerpable[target_type]
|
||||||
|
|
||||||
|
if lerp == nil then
|
||||||
|
springs[data] = nil
|
||||||
|
throw(`Cannot animate type { target_type }`)
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local new_time = data.duration + dt
|
||||||
|
local new_alpha = solve(data.period, data.damping_ratio, data.initial_velocity, new_time)
|
||||||
|
|
||||||
|
data.velocity = -(new_alpha - data.alpha)/dt
|
||||||
|
data.alpha = new_alpha
|
||||||
|
data.duration = new_time
|
||||||
|
|
||||||
|
local value = lerp(initial_position, target_position, new_alpha)
|
||||||
|
|
||||||
|
set(output, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return function() return spring, update_springs end
|
||||||
|
|
|
||||||
|
|
@ -319,9 +319,10 @@ end
|
||||||
|
|
||||||
local function print2(v: unknown)
|
local function print2(v: unknown)
|
||||||
type Buffer = { n: number, [number]: string }
|
type Buffer = { n: number, [number]: string }
|
||||||
|
type Cyclic = { [{}]: true }
|
||||||
|
|
||||||
-- overkill concatenationless string buffer
|
-- overkill concatenationless string buffer
|
||||||
local function tos(value: any, stack: number, str: Buffer)
|
local function tos(value: any, stack: number, str: Buffer, cyclic: Cyclic)
|
||||||
local TAB = " "
|
local TAB = " "
|
||||||
local indent = table.concat(table.create(stack, TAB))
|
local indent = table.concat(table.create(stack, TAB))
|
||||||
|
|
||||||
|
|
@ -339,10 +340,18 @@ local function print2(v: unknown)
|
||||||
local n = str.n
|
local n = str.n
|
||||||
str[n + 1] = "{}"
|
str[n + 1] = "{}"
|
||||||
str.n = n + 1
|
str.n = n + 1
|
||||||
else
|
else -- is table
|
||||||
local tabbed_indent = indent .. TAB
|
local tabbed_indent = indent .. TAB
|
||||||
|
|
||||||
str.n += 1
|
str.n += 1
|
||||||
|
|
||||||
|
if cyclic[value] then
|
||||||
|
str[str.n] = color.gray "*cyclic reference*"
|
||||||
|
return
|
||||||
|
else
|
||||||
|
cyclic[value] = true
|
||||||
|
end
|
||||||
|
|
||||||
str[str.n] = "{\n"
|
str[str.n] = "{\n"
|
||||||
|
|
||||||
local i, v = next(value, nil)
|
local i, v = next(value, nil)
|
||||||
|
|
@ -363,7 +372,7 @@ local function print2(v: unknown)
|
||||||
str[n + 1] = " = "
|
str[n + 1] = " = "
|
||||||
str.n = n + 1
|
str.n = n + 1
|
||||||
|
|
||||||
tos(v, stack + 1, str)
|
tos(v, stack + 1, str, cyclic)
|
||||||
|
|
||||||
i, v = next(value, i)
|
i, v = next(value, i)
|
||||||
|
|
||||||
|
|
@ -380,7 +389,8 @@ local function print2(v: unknown)
|
||||||
end
|
end
|
||||||
|
|
||||||
local str = { n = 0 }
|
local str = { n = 0 }
|
||||||
tos(v, 0, str)
|
local cyclic = {}
|
||||||
|
tos(v, 0, str, cyclic)
|
||||||
print(table.concat(str))
|
print(table.concat(str))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
195
test/tests.luau
195
test/tests.luau
|
|
@ -1,12 +1,12 @@
|
||||||
----------------------------------------------------------------------------------------------------------------------
|
local testkit = require("test/testkit")
|
||||||
-- unit.lua
|
local TEST, CASE, CHECK, FINISH, SKIP = testkit.test()
|
||||||
----------------------------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
local TEST, CASE, CHECK, FINISH, SKIP = require("test/testkit").test()
|
|
||||||
|
|
||||||
local mock = require "test/mock"
|
|
||||||
local Signal = require "test/goodsignal"
|
local Signal = require "test/goodsignal"
|
||||||
local Instance, Vector3, Color3, Vector2, UDim2 = mock.Instance, mock.Vector3, mock.Color3, mock.Vector2, mock.UDim2
|
local mock = require "test/mock"
|
||||||
|
|
||||||
|
|
||||||
|
local Instance, Vector3, Color3, Vector2, UDim2 =
|
||||||
|
mock.Instance, mock.Vector3, mock.Color3, mock.Vector2, mock.UDim2
|
||||||
|
|
||||||
local vide = require "src/init"
|
local vide = require "src/init"
|
||||||
|
|
||||||
|
|
@ -187,23 +187,23 @@ TEST("graph", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("wrap()", function()
|
TEST("source()", function()
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
local watch = vide.watch
|
local watch = vide.watch
|
||||||
|
|
||||||
do CASE "Wrap value"
|
do CASE "source value"
|
||||||
local state = wrap(1)
|
local state = source(1)
|
||||||
CHECK(state() == 1)
|
CHECK(state() == 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Setter"
|
do CASE "Setter"
|
||||||
local state = wrap(1)
|
local state = source(1)
|
||||||
state(2) -- set directly
|
state(2) -- set directly
|
||||||
CHECK(state() == 2)
|
CHECK(state() == 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Does not update if same value"
|
do CASE "Does not update if same value"
|
||||||
local state = wrap(1)
|
local state = source(1)
|
||||||
|
|
||||||
local updates = -1
|
local updates = -1
|
||||||
watch(function()
|
watch(function()
|
||||||
|
|
@ -218,11 +218,11 @@ TEST("wrap()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("derive()", function()
|
TEST("derive()", function()
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
local derive = vide.derive
|
local derive = vide.derive
|
||||||
|
|
||||||
do CASE "Derive new value on state change"
|
do CASE "Derive new value on state change"
|
||||||
local state = wrap(1)
|
local state = source(1)
|
||||||
|
|
||||||
local derived = derive(function()
|
local derived = derive(function()
|
||||||
return tostring(state())
|
return tostring(state())
|
||||||
|
|
@ -235,7 +235,7 @@ TEST("derive()", function()
|
||||||
|
|
||||||
do CASE "Derive from updated"
|
do CASE "Derive from updated"
|
||||||
do
|
do
|
||||||
local a = wrap(1)
|
local a = source(1)
|
||||||
|
|
||||||
local b = derive(function()
|
local b = derive(function()
|
||||||
return a() + 1
|
return a() + 1
|
||||||
|
|
@ -253,7 +253,7 @@ TEST("derive()", function()
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
do CASE "Cleanup"
|
do CASE "Cleanup"
|
||||||
local count, set = wrap(1)
|
local count, set = source(1)
|
||||||
|
|
||||||
local derived = derive(function(from)
|
local derived = derive(function(from)
|
||||||
return { Value = from(count), Destroyed = false }
|
return { Value = from(count), Destroyed = false }
|
||||||
|
|
@ -271,7 +271,7 @@ TEST("derive()", function()
|
||||||
|
|
||||||
do CASE "Garbage collection"
|
do CASE "Garbage collection"
|
||||||
do -- check that `b` does not allow gc of `a`
|
do -- check that `b` does not allow gc of `a`
|
||||||
local a = wrap(1)
|
local a = source(1)
|
||||||
|
|
||||||
local _b = derive(function()
|
local _b = derive(function()
|
||||||
return a()
|
return a()
|
||||||
|
|
@ -286,7 +286,7 @@ TEST("derive()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do -- check that `a` allows gc of `b`
|
do -- check that `a` allows gc of `b`
|
||||||
local a = wrap(1)
|
local a = source(1)
|
||||||
|
|
||||||
local b = derive(function()
|
local b = derive(function()
|
||||||
return a()
|
return a()
|
||||||
|
|
@ -303,7 +303,7 @@ TEST("derive()", function()
|
||||||
do CASE "Garbage collection 2"
|
do CASE "Garbage collection 2"
|
||||||
-- creats a chain `a -> b -> c` where `a` is the root
|
-- creats a chain `a -> b -> c` where `a` is the root
|
||||||
local function setup()
|
local function setup()
|
||||||
local a = wrap(1)
|
local a = source(1)
|
||||||
|
|
||||||
local b = derive(function()
|
local b = derive(function()
|
||||||
return a()
|
return a()
|
||||||
|
|
@ -350,12 +350,12 @@ TEST("derive()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("watch()", function()
|
TEST("watch()", function()
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
local watch = vide.watch
|
local watch = vide.watch
|
||||||
|
|
||||||
do CASE "Capture states"
|
do CASE "Capture states"
|
||||||
local a = wrap(1)
|
local a = source(1)
|
||||||
local b = wrap(1)
|
local b = source(1)
|
||||||
local runcount = 0
|
local runcount = 0
|
||||||
|
|
||||||
watch(function()
|
watch(function()
|
||||||
|
|
@ -372,8 +372,8 @@ TEST("watch()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Stop watch"
|
do CASE "Stop watch"
|
||||||
local a = wrap(1)
|
local a = source(1)
|
||||||
local b = wrap(1)
|
local b = source(1)
|
||||||
local runcount = 0
|
local runcount = 0
|
||||||
|
|
||||||
local unwatch = watch(function()
|
local unwatch = watch(function()
|
||||||
|
|
@ -388,7 +388,7 @@ TEST("watch()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Side-effect cleanup"
|
do CASE "Side-effect cleanup"
|
||||||
local state = wrap(1)
|
local state = source(1)
|
||||||
|
|
||||||
local effect_runcount = 0
|
local effect_runcount = 0
|
||||||
local cleanup_runcount = 0
|
local cleanup_runcount = 0
|
||||||
|
|
@ -417,7 +417,7 @@ TEST("watch()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do -- state prevents gc of watcher
|
do -- state prevents gc of watcher
|
||||||
local state = wrap(1)
|
local state = source(1)
|
||||||
|
|
||||||
local wref
|
local wref
|
||||||
|
|
||||||
|
|
@ -433,7 +433,7 @@ TEST("watch()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do -- watcher can gc if stopped
|
do -- watcher can gc if stopped
|
||||||
local state = wrap(1)
|
local state = source(1)
|
||||||
local effect = factory(state)
|
local effect = factory(state)
|
||||||
|
|
||||||
local unwatch = watch(effect)
|
local unwatch = watch(effect)
|
||||||
|
|
@ -453,7 +453,7 @@ TEST("watch()", function()
|
||||||
local wref
|
local wref
|
||||||
|
|
||||||
do
|
do
|
||||||
local state = wrap(1)
|
local state = source(1)
|
||||||
local effect = factory(state)
|
local effect = factory(state)
|
||||||
watch(effect)
|
watch(effect)
|
||||||
wref = weak { state }
|
wref = weak { state }
|
||||||
|
|
@ -480,7 +480,7 @@ end)
|
||||||
|
|
||||||
TEST("create()", function()
|
TEST("create()", function()
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
|
|
||||||
do CASE "Apply default properties"
|
do CASE "Apply default properties"
|
||||||
local defaults = require("src/defaults")
|
local defaults = require("src/defaults")
|
||||||
|
|
@ -528,8 +528,8 @@ TEST("create()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Binding properties to state"
|
do CASE "Binding properties to state"
|
||||||
local name = wrap("Hi")
|
local name = source("Hi")
|
||||||
local text = wrap("Bye")
|
local text = source("Bye")
|
||||||
|
|
||||||
local label = create "TextLabel" {
|
local label = create "TextLabel" {
|
||||||
Name = name,
|
Name = name,
|
||||||
|
|
@ -548,7 +548,7 @@ TEST("create()", function()
|
||||||
|
|
||||||
do CASE "Binding garbage collection"
|
do CASE "Binding garbage collection"
|
||||||
do -- instance should gc despite property bound to state
|
do -- instance should gc despite property bound to state
|
||||||
local state = wrap("Hi")
|
local state = source("Hi")
|
||||||
|
|
||||||
local wref = weak {
|
local wref = weak {
|
||||||
create "TextLabel" {
|
create "TextLabel" {
|
||||||
|
|
@ -561,7 +561,7 @@ TEST("create()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do -- instance should NOT gc despite property bound to state when parented
|
do -- instance should NOT gc despite property bound to state when parented
|
||||||
local state = wrap("Hi")
|
local state = source("Hi")
|
||||||
|
|
||||||
local parent = create "Frame" {}
|
local parent = create "Frame" {}
|
||||||
|
|
||||||
|
|
@ -588,18 +588,20 @@ TEST("create()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do -- state should not gc once exits scope while instance still exists
|
do -- state should not gc once exits scope while instance still exists
|
||||||
local _label
|
local label
|
||||||
local wref
|
local wref
|
||||||
|
|
||||||
do
|
do
|
||||||
local state = wrap("Hi")
|
local state = source("Hi")
|
||||||
wref = weak { state }
|
label = create "TextLabel" {
|
||||||
_label = create "TextLabel" {
|
|
||||||
Name = state,
|
Name = state,
|
||||||
}
|
}
|
||||||
|
wref = weak { state :: any, label }
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
|
CHECK(wref[2])
|
||||||
CHECK(wref[1])
|
CHECK(wref[1])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -607,7 +609,7 @@ TEST("create()", function()
|
||||||
local wref
|
local wref
|
||||||
|
|
||||||
do
|
do
|
||||||
local text = wrap("Hi")
|
local text = source("Hi")
|
||||||
|
|
||||||
local box = create "TextLabel" {
|
local box = create "TextLabel" {
|
||||||
Text = text,
|
Text = text,
|
||||||
|
|
@ -624,7 +626,7 @@ TEST("create()", function()
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
do -- binding should gc despite state still existing after instance is gc
|
do -- binding should gc despite state still existing after instance is gc
|
||||||
local state = wrap("Hi")
|
local state = source("Hi")
|
||||||
|
|
||||||
do
|
do
|
||||||
local instance = create "TextLabel" {
|
local instance = create "TextLabel" {
|
||||||
|
|
@ -650,7 +652,7 @@ TEST("create()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Bind same state to multiple instance properties"
|
do CASE "Bind same state to multiple instance properties"
|
||||||
local state = wrap "1"
|
local state = source "1"
|
||||||
|
|
||||||
local text = create "TextBox" {
|
local text = create "TextBox" {
|
||||||
Name = state,
|
Name = state,
|
||||||
|
|
@ -666,7 +668,7 @@ TEST("create()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Bind children"
|
do CASE "Bind children"
|
||||||
local state = wrap({} :: {}?)
|
local state = source({} :: {}?)
|
||||||
|
|
||||||
local a, b, c =
|
local a, b, c =
|
||||||
create "TextLabel" { Name = "A" },
|
create "TextLabel" { Name = "A" },
|
||||||
|
|
@ -699,7 +701,7 @@ TEST("create()", function()
|
||||||
do CASE "Parent set to nil by state does not allow gc"
|
do CASE "Parent set to nil by state does not allow gc"
|
||||||
-- this is technically a bug but we test for this anyways to confirm behavior
|
-- this is technically a bug but we test for this anyways to confirm behavior
|
||||||
local parent = create "Frame" { Name = "Parent" }
|
local parent = create "Frame" { Name = "Parent" }
|
||||||
local state = wrap(parent :: Frame?)
|
local state = source(parent :: Frame?)
|
||||||
|
|
||||||
do
|
do
|
||||||
wref.child = create "Frame" { Parent = state, Name = "Child" } :: Frame?
|
wref.child = create "Frame" { Parent = state, Name = "Child" } :: Frame?
|
||||||
|
|
@ -721,35 +723,38 @@ TEST("create()", function()
|
||||||
]]
|
]]
|
||||||
|
|
||||||
do CASE "GC test"
|
do CASE "GC test"
|
||||||
local data = setmetatable({}, {})
|
local wref
|
||||||
local proxy = setmetatable({}, { __mode = "v" })
|
|
||||||
|
|
||||||
local ref = setmetatable({}, { __mode = "v" })
|
do
|
||||||
|
local data = setmetatable({}, {})
|
||||||
|
local proxy = setmetatable({}, { __mode = "v" })
|
||||||
|
|
||||||
--proxy.data = data --? (this line should not affect outcome)
|
local ref = setmetatable({}, { __mode = "v" })
|
||||||
-- `data` strongly references `proxy`
|
|
||||||
data.connection = proxy
|
|
||||||
|
|
||||||
-- `ref` strongly references `data`
|
--proxy.data = data --? (this line should not affect outcome)
|
||||||
ref[data] = proxy
|
-- `data` strongly references `proxy`
|
||||||
|
data.connection = proxy
|
||||||
|
|
||||||
-- although `ref` is weak to values, `data` keeps `proxy` alive
|
-- `ref` strongly references `data`
|
||||||
-- this forms a sort of cyclic reference that the luau gc is unable to detect
|
ref[data] = proxy
|
||||||
|
|
||||||
wref.data = data
|
-- although `ref` is weak to values, `data` keeps `proxy` alive
|
||||||
wref.proxy = proxy
|
-- this forms a sort of cyclic reference that the luau gc is unable to detect
|
||||||
data = nil :: any
|
|
||||||
proxy = nil :: any
|
wref = { data = data, proxy = proxy }
|
||||||
|
end
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
|
|
||||||
CHECK(wref.data and wref.proxy)
|
CHECK(wref.data and wref.proxy)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("map()", function()
|
TEST("map()", function()
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
local unwrap = vide.unwrap
|
local unsource = vide.unsource
|
||||||
local map = vide.map
|
local map = vide.map
|
||||||
|
|
||||||
do CASE "Use integer"
|
do CASE "Use integer"
|
||||||
|
|
@ -777,7 +782,7 @@ TEST("map()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Use state"
|
do CASE "Use state"
|
||||||
local state = wrap { 1, 2, 3 }
|
local state = source { 1, 2, 3 }
|
||||||
|
|
||||||
local derived = map(state, function(_, v)
|
local derived = map(state, function(_, v)
|
||||||
return tostring(v)
|
return tostring(v)
|
||||||
|
|
@ -791,7 +796,7 @@ TEST("map()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Cache result"
|
do CASE "Cache result"
|
||||||
local state, set = wrap { 1, 2, 3 }
|
local state, set = source { 1, 2, 3 }
|
||||||
|
|
||||||
local runcount = table.create(3, 0)
|
local runcount = table.create(3, 0)
|
||||||
|
|
||||||
|
|
@ -815,7 +820,7 @@ TEST("map()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Removal reflected"
|
do CASE "Removal reflected"
|
||||||
local state, set = wrap { 1, 2, 3 }
|
local state, set = source { 1, 2, 3 }
|
||||||
|
|
||||||
local derived = map(state, function(i, v)
|
local derived = map(state, function(i, v)
|
||||||
return tostring(v)
|
return tostring(v)
|
||||||
|
|
@ -835,7 +840,7 @@ TEST("map()", function()
|
||||||
local Children = vide.Children
|
local Children = vide.Children
|
||||||
|
|
||||||
do CASE "Bind children"
|
do CASE "Bind children"
|
||||||
local state, set = wrap { "A", "B", "C" }
|
local state, set = source { "A", "B", "C" }
|
||||||
|
|
||||||
local derived = map(state, function(i, v)
|
local derived = map(state, function(i, v)
|
||||||
return create "TextLabel" {
|
return create "TextLabel" {
|
||||||
|
|
@ -866,7 +871,7 @@ TEST("map()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Use optional destructor"
|
do CASE "Use optional destructor"
|
||||||
local state, set = wrap { 1, 2, 3 }
|
local state, set = source { 1, 2, 3 }
|
||||||
local derived = map(state, function(i, v)
|
local derived = map(state, function(i, v)
|
||||||
return { Value = v, Destroyed = false }
|
return { Value = v, Destroyed = false }
|
||||||
end, function(v)
|
end, function(v)
|
||||||
|
|
@ -888,7 +893,7 @@ TEST("map()", function()
|
||||||
|
|
||||||
do CASE "Garbage collection"
|
do CASE "Garbage collection"
|
||||||
do -- check that `derived` does not allow gc of `state`
|
do -- check that `derived` does not allow gc of `state`
|
||||||
local state = wrap {}
|
local state = source {}
|
||||||
|
|
||||||
local derived = map(state, function(i, v)
|
local derived = map(state, function(i, v)
|
||||||
return v
|
return v
|
||||||
|
|
@ -902,7 +907,7 @@ TEST("map()", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do -- check that `state` allows gc of `derived`
|
do -- check that `state` allows gc of `derived`
|
||||||
local state = wrap {}
|
local state = source {}
|
||||||
|
|
||||||
local derived = map(state, function(i, v)
|
local derived = map(state, function(i, v)
|
||||||
return i, v
|
return i, v
|
||||||
|
|
@ -917,33 +922,15 @@ TEST("map()", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("apply()", function()
|
|
||||||
local apply = vide.apply
|
|
||||||
|
|
||||||
-- uses same application method as `create()` internally, further testing unnecessary
|
|
||||||
do CASE "Apply properties"
|
|
||||||
local part = Instance.new("Part") :: Part
|
|
||||||
|
|
||||||
apply(part) {
|
|
||||||
Position = Vector3.new(1, 1, 1),
|
|
||||||
Color = Color3.new(1, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK(part.Position == Vector3.new(1, 1, 1))
|
|
||||||
CHECK(part.Color == Color3.new(1, 0, 0))
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
TEST("spring()", function()
|
TEST("spring()", function()
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
local unwrap = vide.unwrap
|
|
||||||
local spring = vide.spring
|
local spring = vide.spring
|
||||||
|
|
||||||
do CASE "Update state (on next hearbeat resumption cycle)"
|
do CASE "Update state (on next hearbeat resumption cycle)"
|
||||||
local number, set = wrap(10)
|
local value = source(10)
|
||||||
local springed = spring(number, 1, 1)
|
local springed = spring(value, 1, 1)
|
||||||
|
|
||||||
set(20)
|
value(20)
|
||||||
CHECK(springed() == 10)
|
CHECK(springed() == 10)
|
||||||
vide.step(1/60)
|
vide.step(1/60)
|
||||||
CHECK(springed() ~= 10)
|
CHECK(springed() ~= 10)
|
||||||
|
|
@ -952,7 +939,7 @@ TEST("spring()", function()
|
||||||
|
|
||||||
do CASE "Garbage collection"
|
do CASE "Garbage collection"
|
||||||
do -- `spring` should not allow gc of `state`
|
do -- `spring` should not allow gc of `state`
|
||||||
local state = wrap(10)
|
local state = source(10)
|
||||||
local _springed = spring(state, 1, 1)
|
local _springed = spring(state, 1, 1)
|
||||||
|
|
||||||
wref.state, state = state, nil :: any
|
wref.state, state = state, nil :: any
|
||||||
|
|
@ -962,9 +949,9 @@ TEST("spring()", function()
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
do -- `number` should allow gc of `spring`
|
do -- `value` should allow gc of `spring`
|
||||||
local number = wrap(10)
|
local value = source(10)
|
||||||
local springed = spring(number, 1, 1) :: State?
|
local springed = spring(value, 1, 1) :: State?
|
||||||
|
|
||||||
wref.springed, springed = springed, nil
|
wref.springed, springed = springed, nil
|
||||||
|
|
||||||
|
|
@ -977,7 +964,7 @@ TEST("spring()", function()
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
|
|
||||||
do CASE "Garbage collection (binded)"
|
do CASE "Garbage collection (binded)"
|
||||||
local number = wrap(10)
|
local number = source(10)
|
||||||
local springed = spring(number, 1, 1) :: State?
|
local springed = spring(number, 1, 1) :: State?
|
||||||
|
|
||||||
local _label = create "TextLabel" {
|
local _label = create "TextLabel" {
|
||||||
|
|
@ -994,7 +981,7 @@ end)
|
||||||
TEST("Event", function()
|
TEST("Event", function()
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
local Event = vide.Event
|
local Event = vide.Event
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
|
|
||||||
do CASE "Connect event"
|
do CASE "Connect event"
|
||||||
local connected = false
|
local connected = false
|
||||||
|
|
@ -1018,7 +1005,7 @@ TEST("Event", function()
|
||||||
do CASE "Bind connection to state"
|
do CASE "Bind connection to state"
|
||||||
local countA = 0
|
local countA = 0
|
||||||
local countB = 0
|
local countB = 0
|
||||||
local listener, set = wrap(function() countA += 1 end :: () -> ()?)
|
local listener, set = source(function() countA += 1 end :: () -> ()?)
|
||||||
|
|
||||||
local event = (Signal.new() :: any) :: RBXScriptSignal & { Fire: any }
|
local event = (Signal.new() :: any) :: RBXScriptSignal & { Fire: any }
|
||||||
|
|
||||||
|
|
@ -1049,7 +1036,7 @@ end)
|
||||||
TEST("Changed", function()
|
TEST("Changed", function()
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
local Changed = vide.Changed
|
local Changed = vide.Changed
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
|
|
||||||
do CASE "Connects event"
|
do CASE "Connects event"
|
||||||
local connected = false
|
local connected = false
|
||||||
|
|
@ -1067,7 +1054,7 @@ TEST("Changed", function()
|
||||||
do CASE "Bind connection to state"
|
do CASE "Bind connection to state"
|
||||||
local countA = 0
|
local countA = 0
|
||||||
local countB = 0
|
local countB = 0
|
||||||
local listener, set = wrap(function() countA += 1 end :: () -> ()?)
|
local listener, set = source(function() countA += 1 end :: () -> ()?)
|
||||||
|
|
||||||
|
|
||||||
local label = create "TextLabel" {
|
local label = create "TextLabel" {
|
||||||
|
|
@ -1118,13 +1105,13 @@ end)]]
|
||||||
TEST("strict", function()
|
TEST("strict", function()
|
||||||
vide.strict = true
|
vide.strict = true
|
||||||
|
|
||||||
local wrap = vide.wrap
|
local source = vide.source
|
||||||
local unwrap = vide.unwrap
|
local unsource = vide.unsource
|
||||||
local derive = vide.derive
|
local derive = vide.derive
|
||||||
local watch = vide.watch
|
local watch = vide.watch
|
||||||
|
|
||||||
do CASE "Error on derived callback yield"
|
do CASE "Error on derived callback yield"
|
||||||
local state = wrap(1)
|
local state = source(1)
|
||||||
|
|
||||||
local ok = pcall(function()
|
local ok = pcall(function()
|
||||||
local _derived = derive(function(from)
|
local _derived = derive(function(from)
|
||||||
|
|
@ -1137,7 +1124,7 @@ TEST("strict", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Error on watcher callback yield"
|
do CASE "Error on watcher callback yield"
|
||||||
local state = wrap(1)
|
local state = source(1)
|
||||||
|
|
||||||
local ok = pcall(function()
|
local ok = pcall(function()
|
||||||
local _derived = watch(function(from)
|
local _derived = watch(function(from)
|
||||||
|
|
@ -1150,7 +1137,7 @@ TEST("strict", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Run derived callback twice"
|
do CASE "Run derived callback twice"
|
||||||
local state, set = wrap(1)
|
local state, set = source(1)
|
||||||
local runcount = 0
|
local runcount = 0
|
||||||
|
|
||||||
local derived = derive(function(from)
|
local derived = derive(function(from)
|
||||||
|
|
@ -1165,7 +1152,7 @@ TEST("strict", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Run watcher callback twice"
|
do CASE "Run watcher callback twice"
|
||||||
local state, set = wrap(1)
|
local state, set = source(1)
|
||||||
local runcount = 0
|
local runcount = 0
|
||||||
|
|
||||||
watch(function(from)
|
watch(function(from)
|
||||||
|
|
@ -1192,7 +1179,7 @@ TEST("strict", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "Does not allow same table set"
|
do CASE "Does not allow same table set"
|
||||||
local _, set = wrap()
|
local _, set = source()
|
||||||
|
|
||||||
local t = {}
|
local t = {}
|
||||||
|
|
||||||
|
|
|
||||||
64
todo.md
64
todo.md
|
|
@ -1,6 +1,60 @@
|
||||||
# Todo
|
```lua
|
||||||
|
function TextInput(p: {
|
||||||
|
DefaultText: string,
|
||||||
|
Output: (string) -> ()
|
||||||
|
} & Layout)
|
||||||
|
return create "TextBox" {
|
||||||
|
Layout = p.Layout,
|
||||||
|
Children = p.Children
|
||||||
|
|
||||||
|
BackgroundText = DefaultText,
|
||||||
|
|
||||||
|
[{"Changed"}] = function(self)
|
||||||
|
p.Output(self.Text)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Counter()
|
||||||
|
local count = source(0)
|
||||||
|
|
||||||
|
return create "TextButton" {
|
||||||
|
Text = count
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
source
|
||||||
|
derive
|
||||||
|
map
|
||||||
|
|
||||||
|
spring
|
||||||
|
```
|
||||||
|
|
||||||
|
onCleanup
|
||||||
|
Index
|
||||||
|
For
|
||||||
|
untrack
|
||||||
|
batch
|
||||||
|
|
||||||
|
## version 1
|
||||||
|
|
||||||
|
```lua
|
||||||
|
map(items, function(item, i)
|
||||||
|
return Item {
|
||||||
|
Item = item, -- primitive
|
||||||
|
LayoutOrder = i
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
## version 2
|
||||||
|
|
||||||
|
```lua
|
||||||
|
each(items, function(item, i)
|
||||||
|
return Item {
|
||||||
|
Item = item, -- state
|
||||||
|
LayoutOrder = i
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
- investigate solid js `for` and batching
|
|
||||||
- equality checking of derived state updates to prevent redundant updates of derived state
|
|
||||||
- watcher callbacks when watcher watches a parent and child
|
|
||||||
- stores
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue