Update property nesting

Deeper nested properties guaranteed to be processed after shallower
nested properties.

Also added strict mode checks for duplicate nested properties.
This commit is contained in:
Aaron Smith 2023-08-14 11:42:16 +01:00
parent ddb88cfdf8
commit a769139136
5 changed files with 125 additions and 29 deletions

View file

@ -43,7 +43,7 @@ export default defineConfig({
{ text: "Source", link: "/tut/crash-course/4-source" },
{ text: "Derived Source", link: "/tut/crash-course/5-derived-source" },
{ text: "Table Source", link: "/tut/crash-course/6-table-source" },
{ text: "Property Groups", link: "/tut/crash-course/7-property-groups" },
{ text: "Nested Properties", link: "/tut/crash-course/7-nested-properties" },
{ text: "Actions", link: "/tut/crash-course/8-actions" },
]
},

View file

@ -15,7 +15,8 @@ Currently, strict mode will:
2. Run watchers twice when a source updates.
3. Throw an error if yields occur where they are not allowed.
4. Checks for `indexes()` and `values()` returning primitive values.
5. Better error reporting and stack traces.
5. Checks for duplicate nested properties at same depth.
6. Better error reporting and stack traces.
By rerunning sources and watchers, any side-effects are made more apparent.
This also helps ensure that cleanups are being handled correctly.

View file

@ -1,4 +1,4 @@
# Property Groups
# Nested Properties
Often when creating components from existing components, you can find yourself
repetitively passing through properties such as size or position.
@ -99,3 +99,22 @@ List {
}
}
```
Deeper nested properties are guaranteed to be set after shallower nested
properties, this can be used to create overridable default properties.
```lua
local function CenteredList(props: Children & Layout)
return List {
Layout = {
props.Layout,
-- can be overriden by `props.Layout`
AnchorPoint = Vector2.new(0.5, 0),
Position = UDim2.fromScale(0.5, 0)
},
Children = props.Children
}
end
```

View file

@ -5,11 +5,12 @@ if not game then
typeof = require "test/mock".typeof
end
local graph = require(script.Parent.graph)
type Node<T> = graph.Node<T>
local flags = require(script.Parent.flags)
local throw = require(script.Parent.throw)
local bind = require(script.Parent.bind)
local _, is_action = require(script.Parent.action)()
local graph = require(script.Parent.graph)
type Node<T> = graph.Node<T>
local event_buffer: { [string]: () -> () } = {}
local action_buffers = {} :: { { () -> () } }
@ -21,15 +22,42 @@ setmetatable(action_buffers :: any, {
end
})
local function recurse(instance: Instance, properties: { [unknown]: unknown })
local nested_debug_cache: { [number]: { [string]: true } } = {}
setmetatable(nested_debug_cache :: any, {
__index = function(_, i: number)
nested_debug_cache[i] = {}
return nested_debug_cache[i]
end
})
local nested_stack = {} :: { {} | number }
local function process(instance: Instance, properties: { [unknown]: unknown })
local strict = flags.strict
table.clear(nested_stack)
if strict then table.clear(nested_debug_cache) end
local depth = 1
repeat
for property, value in properties do
if type(value) == "table" then
if is_action(value) then
table.insert(action_buffers[(value :: any).priority], (value :: any).callback :: () -> ())
else
recurse(instance, value :: {})
table.insert(nested_stack, depth + 1)
table.insert(nested_stack, value :: {})
end
elseif type(property) == "string" then
if strict then
if nested_debug_cache[depth][property] then
throw(`duplicate property {property} at depth {depth}`)
end
nested_debug_cache[depth][property] = true
end
if type(value) == "function" then
if typeof((instance :: any)[property]) == "RBXScriptSignal" then
event_buffer[property] = value :: () -> ()
@ -47,6 +75,11 @@ local function recurse(instance: Instance, properties: { [unknown]: unknown })
end
end
end
properties = table.remove(nested_stack) :: {}
depth = table.remove(nested_stack) :: number
until not properties
end
local function apply<T>(instance: T & Instance, properties: { [unknown]: unknown }): T
@ -58,7 +91,7 @@ local function apply<T>(instance: T & Instance, properties: { [unknown]: unknown
table.clear(buffer)
end
recurse(instance, properties)
process(instance, properties)
for event, fn in next, event_buffer do
(instance :: any)[event]:Connect(fn)

View file

@ -596,6 +596,20 @@ TEST("create()", function()
CHECK(text.Text == "test")
end
do CASE "nested precedence"
local text = create "TextLabel" {
Group = {
Text = "1",
{
Text = "2"
}
}
}
CHECK(text.Text == "2")
end
do CASE "independent"
local frame = create "Frame"
CHECK(frame {} ~= frame {})
@ -1153,6 +1167,7 @@ end)
TEST("strict", function()
vide.strict = true
local create = vide.create
local source = vide.source
local derive = vide.derive
local watch = vide.watch
@ -1231,6 +1246,34 @@ TEST("strict", function()
CHECK(not ok)
end
do CASE "duplicate properties"
local ok = pcall(function()
create "TextLabel" {
{
Name = "foo"
},
{
Name = "bar"
}
}
end)
CHECK(not ok)
ok = pcall(function()
create "TextLabel" {
{
Name = "foo",
{
Name = "bar"
}
}
}
end)
CHECK(ok)
end
end)
local ok = FINISH()