Add aggregate initialization

This commit is contained in:
Aaron Smith 2023-08-22 11:15:27 +01:00
parent be15f69522
commit 2aebaf5abb
9 changed files with 139 additions and 36 deletions

View file

@ -28,15 +28,17 @@ Creates a new UI element, applying any given properties.
- **Property setting rules**
- If a table value is another table, that nested table is processed so that
any properties inside that table are also applied to the instance just
like the outer table.
- If a table index is a string:
- If its value is a table then it will attempt to perform aggregate
initialization.
- If its value is a function then it will either bind that property to
the function or connect it if the property type is a `RBXScriptSignal`.
- If the value is not a function then the property will be set to that
value.
- If a table index is a number:
- If its value is a table then that table will be recursively
- processed just like the outer table.
- If its value is a function then it will parent and bind any instances
returned by that function as children.
- If its value is an instance then it will be parented to the instance.

View file

@ -54,3 +54,17 @@ create "TextButton" {
end
}
```
You can also use a form of aggregate initialization to create datatypes instead
of explicitly typing out the class name and constructor.
```lua
create "Frame" {
AnchorPoint = { 0.5, 1 },
UDim2 = { 0.5, 0, 0.5, 0}
}
```
When a property is assigned a table, Vide will inspect the type of the property
being assigned to, and call that type's default `new()` constructor with the
values from the unpacked table.

View file

@ -1,8 +1,12 @@
local typeof = typeof
local Vector2 = Vector2
local UDim2 = UDim2
if not game then
script = require "test/relative-string"
typeof = require "test/mock".typeof
Vector2 = require "test/mock".Vector2
UDim2 = require "test/mock".UDim2
end
local flags = require(script.Parent.flags)
@ -33,6 +37,19 @@ setmetatable(nested_debug_cache :: any, {
local nested_stack = {} :: { {} | number }
-- todo
local classes = {
Vector2 = Vector2,
UDim2 = UDim2,
UDim = UDim2,
Rect = Rect,
Color3 = Color3
}
local function get_class(v: unknown): { new: (...any) -> () }
return classes[typeof(v)]
end
local function process(instance: Instance, properties: { [unknown]: unknown })
local strict = flags.strict
@ -43,14 +60,7 @@ local function process(instance: Instance, properties: { [unknown]: unknown })
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
table.insert(nested_stack, depth + 1)
table.insert(nested_stack, value :: {})
end
elseif type(property) == "string" then
if type(property) == "string" then
if strict then
if nested_debug_cache[depth][property] then
throw(`duplicate property {property} at depth {depth}`)
@ -58,7 +68,13 @@ local function process(instance: Instance, properties: { [unknown]: unknown })
nested_debug_cache[depth][property] = true
end
if type(value) == "function" then
if type(value) == "table" then
local class = get_class((instance :: any)[property])
if class == nil then
throw(`cannot aggregate construct type {typeof(value)} for property {property}`)
end
(instance :: any)[property] = class.new(unpack(value :: {}))
elseif type(value) == "function" then
if typeof((instance :: any)[property]) == "RBXScriptSignal" then
event_buffer[property] = value :: () -> ()
else
@ -70,6 +86,13 @@ local function process(instance: Instance, properties: { [unknown]: unknown })
elseif type(property) == "number" then
if type(value) == "function" then
bind.children(instance, value :: () -> { Instance })
elseif type(value) == "table" then
if is_action(value) then
table.insert(action_buffers[(value :: any).priority], (value :: any).callback :: () -> ())
else
table.insert(nested_stack, depth + 1)
table.insert(nested_stack, value :: {})
end
else
(value :: Instance).Parent = instance
end

View file

@ -1,18 +1,16 @@
local Enum = Enum
local Color3 = Color3
local Vector3 = Vector3
if not game then
local mock = require "test/mock"
Enum = mock.Enum
Color3 = mock.Color3
Vector3 = mock.Vector3
end
return {
Part = {
Material = Enum.Material.SmoothPlastic,
Size = Vector3.new(1, 1, 1),
--Size = Vector3.new(1, 1, 1),
Anchored = true
},

View file

@ -1,7 +1,6 @@
local function get_path(s)
if string.sub(s, #s - 4, #s) == ".luau" then
s = string.sub(s, 1, #s - 5)
print("test, ", s)
end
return string.split(s, string.match(s, "%w+/") and "/" or ".")

View file

@ -325,4 +325,41 @@ BENCH("cleanup gc removal", function()
vide.step(0) -- cleanup from previous benchmark
end)
do
-- the purpose of the two following benchmarks is to measure the overhead of
-- aggregate construction
BENCH("set explicit mock vector2", function()
local create = vide.create
local apply = require "src/apply"
local Vector2 = require "test/mock".Vector2
local label = create "TextLabel" {
AnchorPoint = Vector2.new(1, 1)
}
for i = 1, START(N) do
apply(label, {
AnchorPoint = Vector2.new(i, i)
})
end
end)
BENCH("set aggregate mock vector2", function()
local create = vide.create
local apply = require "src/apply"
local Vector2 = require "test/mock".Vector2
local label = create "TextLabel" {
AnchorPoint = Vector2.new(1, 1)
}
for i = 1, START(N) do
apply(label, {
AnchorPoint = { i, i }
})
end
end)
end
return nil

View file

@ -217,9 +217,28 @@ local Instance = {} :: any do
end
end
local Color3 = {} :: any do
local function table_to_proxy(t: any)
local proxy = newproxy(true)
local mt = getmetatable(proxy)
for i, v in getmetatable(t) do
mt[i] = v
end
function mt:__index(i)
return t[i]
end
function mt:__newindex(i, v)
t[i] = v
end
return proxy
end
local Color3 = { __type = "Color3" } :: any do
function Color3.new(r, g, b)
return setmetatable({ r = r, g = g, b = b}, Color3)
return table_to_proxy(setmetatable({ r = r, g = g, b = b }, Color3))
end
function Color3.__eq(a, b)
@ -227,19 +246,9 @@ local Color3 = {} :: any do
end
end
local Vector3 = {} :: any do
function Vector3.new(x, y, z)
return setmetatable({ x = x, y = y, z = z}, Vector3)
end
function Vector3.__eq(a, b)
return a.x == b.x and a.y == b.y and a.z == b.z
end
end
local Vector2 = {} :: any do
local Vector2 = { __type = "Vector2" } :: any do
function Vector2.new(x, y)
return setmetatable({ x = x, y = y }, Vector2)
return table_to_proxy(setmetatable({ x = x, y = y }, Vector2))
end
function Vector2.__eq(a, b)
@ -247,9 +256,13 @@ local Vector2 = {} :: any do
end
end
local UDim2 = {} :: any do
local UDim2 = { __type = "UDim2" } :: any do
function UDim2.new(sx, ox, sy, oy)
return table_to_proxy(setmetatable({ x = { scale = sx, offset = ox }, y = { scale = sy, offset = oy } }, UDim2))
end
function UDim2.fromScale(x, y)
return setmetatable({ x = { scale = x, offset = 0 }, y = { scale = y, offset = 0 } }, UDim2)
return table_to_proxy(setmetatable({ x = { scale = x, offset = 0 }, y = { scale = y, offset = 0 } }, UDim2))
end
function UDim2.__eq(a, b)

View file

@ -3,6 +3,7 @@ local TEST, CASE, CHECK, FINISH = testkit.test()
local mock = require "test/mock"
local Instance, Signal = mock.Instance, mock.Signal
local Vector2, UDim2 = mock.Vector2, mock.UDim2
local vide = require "src/init"
@ -598,15 +599,32 @@ TEST("create()", function()
do CASE "set nested properties"
local text = create "TextLabel" {
{ Name = "Label" },
Group = { Text = "test" }
{ Text = "test" }
}
CHECK(text.Name == "Label")
CHECK(text.Text == "test")
end
do CASE "aggregate construction"
local template = create "TextLabel" {
AnchorPoint = Vector2.new(),
Position = UDim2.new()
}
print("template", template.AnchorPoint)
local text = create(template) {
AnchorPoint = { 1, 2 },
Position = { 3, 4 }
}
CHECK(text.AnchorPoint == Vector2.new(1, 2))
CHECK(text.Position == UDim2.new(3, 4))
end
do CASE "nested precedence"
local text = create "TextLabel" {
Group = {
{
Text = "1",
{
@ -634,7 +652,7 @@ TEST("create()", function()
create "TextLabel" { Name = "E" }
}
},
Children = {
{
create "TextLabel" { Name = "F" } :: any,
{ create "TextLabel" { Name = "G" } }
}

View file

@ -5,7 +5,6 @@
- better error reporting and stack traces in strict mode
- auto-enable of strict mode depending on compiler optimizaton level
- check smoothness of spring at high frequency updates
- aggregate initialization of properties
- implement from solid
- [x] onCleanup > `cleanup()`
- [x] Index > `indexes()`