diff --git a/docs/api/creation.md b/docs/api/creation.md index 838284b..8726919 100644 --- a/docs/api/creation.md +++ b/docs/api/creation.md @@ -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. diff --git a/docs/tut/crash-course/2-creation.md b/docs/tut/crash-course/2-creation.md index 8521641..62f7a63 100644 --- a/docs/tut/crash-course/2-creation.md +++ b/docs/tut/crash-course/2-creation.md @@ -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. diff --git a/src/apply.luau b/src/apply.luau index dfcac2e..d65ea7e 100644 --- a/src/apply.luau +++ b/src/apply.luau @@ -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 diff --git a/src/defaults.luau b/src/defaults.luau index b1049d6..d017d35 100644 --- a/src/defaults.luau +++ b/src/defaults.luau @@ -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 }, diff --git a/src/throw.luau b/src/throw.luau index 3058672..41bc8cb 100644 --- a/src/throw.luau +++ b/src/throw.luau @@ -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 ".") diff --git a/test/benchmark.luau b/test/benchmark.luau index d57dc13..4718aae 100644 --- a/test/benchmark.luau +++ b/test/benchmark.luau @@ -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 diff --git a/test/mock.luau b/test/mock.luau index e21b872..b7cf634 100644 --- a/test/mock.luau +++ b/test/mock.luau @@ -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) diff --git a/test/tests.luau b/test/tests.luau index a1cc313..80cef78 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -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" } } } diff --git a/todo.md b/todo.md index e3577ea..486383e 100644 --- a/todo.md +++ b/todo.md @@ -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()`