Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
centau committed Aug 10, 2023
1 parent 2050c25 commit cc10c80
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 220 deletions.
6 changes: 3 additions & 3 deletions docs/api/strict-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ vide.strict = true

What strict mode will do:

1. Run derived callbacks twice when re-evaluating.
2. Run watcher callbacks twice when a state changes.
1. Run derived sources twice a source updates.
2. Run watchers twice when a source updates.
3. Throw an error if yields occur where they are not allowed.
4. Checks for `map()` returning primitive values.
4. Checks for `indexes()` and `values()` returning primitive values.
5. Better error reporting and stack traces.

It is recommend to develop UI with strict mode and to disable it when pushing to
Expand Down
6 changes: 2 additions & 4 deletions src/bind.luau
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function setup(instance: Instance, setter: (Instance) -> ())
local trace = traceback()
setter = function(instance)
local ok, err: string? = pcall(fn, instance)
if not ok then warn(`error occured updating state binding:\n{err}\nset from:{trace}`) end
if not ok then warn(`error occured updating property:\n{err}\nset from:{trace}`) end
end
end

Expand All @@ -68,8 +68,6 @@ function setup(instance: Instance, setter: (Instance) -> ())
instance:GetPropertyChangedSignal("Parent"):Connect(ref)
end

-- todo: move `fn` as arg?

local function bind_property(instance: Instance, property: string, fn: () -> unknown)
setup(instance, function(instance_weak: any)
instance_weak[property] = fn()
Expand All @@ -80,13 +78,13 @@ local function bind_parent(instance: Instance, fn: () -> Instance?)
instance.Destroying:Connect(function()
instance= nil :: any -- allow gc when destroyed
end)

setup(instance, function(instance)
local _ = instance -- state will strongly reference instance when parent is bound
instance.Parent = fn()
end)
end

-- todo: could optimize, see: maps.luau values()
local function bind_children(parent: Instance, fn: () -> { Instance })
local current_child_set: { [Instance]: true } = {} -- cache of all children parented before update
local new_child_set: { [Instance]: true } = {} -- cache of all children parented after update
Expand Down
10 changes: 5 additions & 5 deletions src/create.luau
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ local defaults = require(script.Parent.defaults)
local apply = require(script.Parent.apply)
local memoize = require(script.Parent.memoize)

local function createInstance(className: string)
local function create_instance(className: string)
local success, instance: Instance = pcall(Instance.new, className :: any)
if success == false then throw(`invalid class name, could not create instance of class { className }`) end

Expand All @@ -26,9 +26,9 @@ local function createInstance(className: string)
return function(properties: { [any]: unknown }): Instance
return apply(instance:Clone(), properties)
end
end; createInstance = memoize(createInstance)
end; create_instance = memoize(create_instance)

local function cloneInstance(instance: Instance)
local function clone_instance(instance: Instance)
return function(properties: { [any]: unknown }): Instance
local clone = instance:Clone()
if not clone then error("Attempt to clone a non-archivable instance", 3) end
Expand All @@ -38,9 +38,9 @@ end

local function create(classNameOrInstance: string|Instance)
if type(classNameOrInstance) == "string" then
return createInstance(classNameOrInstance)
return create_instance(classNameOrInstance)
elseif typeof(classNameOrInstance) == "Instance" then
return cloneInstance(classNameOrInstance)
return clone_instance(classNameOrInstance)
else
error("Bad argument #1, expected string or instance, got "..typeof(classNameOrInstance), 2)
end
Expand Down
37 changes: 24 additions & 13 deletions src/graph.luau
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
if not game then script = require "test/wrap-require" end

local throw = require(script.Parent.throw)
local flags = require(script.Parent.flags)

export type Node<T> = {
Expand All @@ -13,28 +14,30 @@ local reff = false
local refs = {} :: { Node<unknown> }

local WEAK_VALUES_RESIZABLE = { __mode = "vs" }
local EVALUATION_ERR = "error while evaluating node:\n\n"
local EVALUATION_ERR = "error while evaluating source:\n\n"

setmetatable(refs :: any, WEAK_VALUES_RESIZABLE)

local check_for_yield do
local check_for_yield: <T...>(fn: (T...) -> unknown, T...) -> () do
local t = { __mode = "kv" }
setmetatable(t, t)

check_for_yield = function<T..., U...>(fn: (T...) -> (), ...: any)
check_for_yield = function(fn, ...: any)
local args = { ... }
t.__unm = function()

t.__unm = function(_)
fn(unpack(args))
end

local ok, err = pcall(function()
return -t :: any
local _ = -t
end)

if not ok then
if err == "attempt to yield across metamethod/C-call boundary" or err == "thread is not yieldable" then
error(EVALUATION_ERR .. "cannot yield when deriving node in watcher", 3)
throw(EVALUATION_ERR .. "cannot yield when deriving node in watcher")
else
error(EVALUATION_ERR..err, 3)
throw(EVALUATION_ERR .. err)
end
end
end
Expand All @@ -45,10 +48,16 @@ local function set_effect<T>(node: Node<unknown>, fn: (T) -> (), key: T)
end

local function run_effects(node: Node<unknown>)
for effect, key in next, node.effects do
if flags.strict then effect(key) end
effect(key)
end
if flags.strict then
for effect, key in next, node.effects do
effect(key)
effect(key)
end
else
for effect, key in next, node.effects do
effect(key)
end
end
end

-- retrieves a node's cached value
Expand All @@ -71,8 +80,10 @@ end
local function update(node: Node<unknown>)
run_effects(node)
if node.children then
local strict = flags.strict

for _, child in node.children do
if flags.strict then check_for_yield(child.derive) end
if strict then check_for_yield(child.derive) end
child.cache = child.derive()
update(child)
end
Expand Down Expand Up @@ -108,7 +119,7 @@ local function capture<T, U>(fn: (U?) -> T, arg: U?): ({ Node<unknown> }, T)

reff = false

if not ok then error("error while detecting watcher: " .. result :: string, 0) end
if not ok then throw(EVALUATION_ERR .. result :: string) end

return refs, result :: T
end
Expand Down
14 changes: 10 additions & 4 deletions src/init.luau
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ local indexes, values = require(script.maps)()
local spring, update_springs = require(script.spring)()
local action = require(script.action)()

local throw = require(script.throw)
local flags = require(script.flags)

local vide = {
Expand Down Expand Up @@ -49,15 +50,20 @@ local vide = {
}

setmetatable(vide :: any, {
__index = function(_, index: unknown)
error(string.format("\"%s\" is not a valid member of vide", tostring(index)), 2)
__index = function(_, index: unknown): ()
if index == "strict" then
return flags.strict
else
throw(`{tostring(index)} is not a valid member of vide`)
end
end,

__newindex = function(_, index: unknown, value: unknown)
if index == "strict" then
flags.strict = if type(value) == "boolean" then value else error("strict must be a boolean", 2)
if value ~= true then throw "strict mode can only be set to true" end
flags.strict = true
else
error(string.format("\"%s\" is not a valid member of vide", tostring(index)), 2)
throw(`{tostring(index)} is not a valid member of vide`)
end
end
})
Expand Down
25 changes: 24 additions & 1 deletion src/maps.luau
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
if not game then script = require "test/wrap-require" end

local throw = require(script.Parent.throw)
local flags = require(script.Parent.flags)
local graph = require(script.Parent.graph)
type Node<T> = graph.Node<T>
local create = graph.create
Expand All @@ -9,6 +11,15 @@ local link = graph.link

type Map<K, V> = { [K]: V }

local function check_primitives(t: {})
if not flags.strict then return end

for _, v in next, t do
if type(v) == "table" or type(v) == "userdata" then continue end
throw("table source map cannot return primitives")
end
end

-- todo: optimize output array
local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI, K) -> VO): () -> { VO }
local input_cache = {} :: Map<K, VI>
Expand Down Expand Up @@ -54,7 +65,8 @@ local function indexes<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI,
for _, v in next, output_cache do
table.insert(output_array, v)
end

check_primitives(output_array)

return output_array
end

Expand Down Expand Up @@ -86,6 +98,16 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->

local function recompute(data: Map<K, VI>)
local cur_input_cache, new_input_cache = cur_input_cache_up, new_input_cache_up

if flags.strict then
local cache = {}
for _, v in next, data do
if cache[v] ~= nil then
throw "duplicate table value detected"
end
cache[v] = true
end
end

-- process data
for i, v in next, data do
Expand Down Expand Up @@ -136,6 +158,7 @@ local function values<K, VI, VO>(input: () -> Map<K, VI>, transform: (VI, () ->
for _, node in next, nodes do
link(node, output, derive)
end
check_primitives(output_array)

output.cache = recompute(value)

Expand Down
8 changes: 4 additions & 4 deletions src/spring.luau
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,21 @@ local function update_springs(dt: number)
typeof(initial_position),
target_type
))
throw(`Cannot tween type { typeof(initial_position) } and { 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 }`)
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)
local new_velocity = -(new_alpha - data.alpha)/dt

local acceleration = (new_velocity - data.velocity)/dt

data.velocity = new_velocity
Expand All @@ -181,7 +180,8 @@ local function update_springs(dt: number)

local value = lerp(initial_position, target_position, new_alpha)

if math.abs(acceleration) < 0.01 then
if math.abs(acceleration) < (0.01 / data.period) then
-- close enough to target, remove and set value to target
table.insert(remove_queue, data)
set(output, target_position)
else
Expand Down
24 changes: 10 additions & 14 deletions test/benchmark.luau
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
------------------------------------------------------------------------------------------
-- benchmark.lua
------------------------------------------------------------------------------------------

local BENCH, START = require("test/testkit").benchmark()

local vide = require "src/init"

local N = 2^18 -- 262144

BENCH("Create state", function()
BENCH("create state", function()
local cache = table.create(N)
local source = vide.source

Expand All @@ -17,23 +13,23 @@ BENCH("Create state", function()
end
end)

BENCH("Get value", function()
BENCH("get value", function()
local state = vide.source(1)

for i = 1, START(N) do
state()
end
end)

BENCH("Set value", function()
BENCH("set value", function()
local state = vide.source(1)

for i = 1, START(N) do
state(i)
end
end)

BENCH("Derive 1 state", function()
BENCH("derive 1 state", function()
local cache = table.create(N)
local state = vide.source(1)
local derive = vide.derive
Expand All @@ -45,7 +41,7 @@ BENCH("Derive 1 state", function()
end
end)

BENCH("Derive 4 states", function()
BENCH("derive 4 states", function()
local cache = table.create(N)
local state = vide.source(1)
local state2 = vide.source(2)
Expand All @@ -60,7 +56,7 @@ BENCH("Derive 4 states", function()
end
end)

BENCH("Set derived value", function()
BENCH("set derived value", function()
local state = vide.source(1)
local _derived = vide.derive(state)

Expand All @@ -69,7 +65,7 @@ BENCH("Set derived value", function()
end
end)

BENCH("Apply 0 properties", function()
BENCH("apply 0 properties", function()
local apply = require "src/apply"
local instance = vide.create("Frame") {}

Expand All @@ -78,7 +74,7 @@ BENCH("Apply 0 properties", function()
end
end)

BENCH("Apply 8 properties", function()
BENCH("apply 8 properties", function()
local apply = require "src/apply"
local instance = vide.create("Frame") {}

Expand All @@ -96,7 +92,7 @@ BENCH("Apply 8 properties", function()
end
end)

BENCH("Bind state", function()
BENCH("bind source", function()
local apply = require "src/apply"
local instance = vide.create("Frame") {}
local state = vide.source(1)
Expand All @@ -108,7 +104,7 @@ BENCH("Bind state", function()
end
end)

BENCH("Update binding", function()
BENCH("update binding", function()
local apply = require "src/apply"
local instance = vide.create("Frame") {}
local state = vide.source(1)
Expand Down
Loading

0 comments on commit cc10c80

Please sign in to comment.