Perf: ImVec2/ImVec4/ImRect store named fields (no metamethod per .x/.Min), bench flags --roblox/--open-only

This commit is contained in:
4DBug
2026-10-08 10:16:40 -05:00
parent 7a20710b65
commit 623dc5b936
9 changed files with 299 additions and 419 deletions
+38 -85
View File
@@ -193,8 +193,8 @@ ImGuiMouseCursor =
--- @return ImTextureRect
function ImTextureRect(x, y, w, h) return { x = x, y = y, w = w, h = h } end
-- This structure supports indexing on string keys `x`, `y` and number keys 1, 2.
-- But note that the former is likely to be more expensive.
-- [Luau] Stored as named fields {x=, y=} so the common `.x`/`.y` access is a plain table read (no metamethod call).
-- Numeric keys 1, 2 still work through the metatable, but are slower: prefer .x/.y in hot code.
--- @class ImVec2
--- @operator add(ImVec2): ImVec2
--- @operator sub(ImVec2): ImVec2
@@ -202,146 +202,97 @@ function ImTextureRect(x, y, w, h) return { x = x, y = y, w = w, h = h } end
--- @operator mul(ImVec2): ImVec2
--- @operator div(number): ImVec2
--- @operator div(ImVec2): ImVec2
--- @field [1] number
--- @field [2] number
--- @field x number
--- @field y number
local IM_VEC2 = {}
local VEC2_KEYS = { "x", "y" }
--- @param t ImVec2
--- @param k string
IM_VEC2.__index = function(t, k)
if k == "x" then return rawget(t, 1)
elseif k == "y" then return rawget(t, 2)
end
local n = VEC2_KEYS[k]
if n then return rawget(t, n) end
end
--- @param t ImVec2
--- @param k string
--- @param v number
IM_VEC2.__newindex = function(t, k, v)
if k == "x" then rawset(t, 1, v)
elseif k == "y" then rawset(t, 2, v)
else IM_ASSERT(false)
end
local n = VEC2_KEYS[k]
IM_ASSERT(n ~= nil)
rawset(t, n, v)
end
--- @param x? number
--- @param y? number
--- @return ImVec2
--- @nodiscard
function ImVec2(x, y) return setmetatable({x or 0, y or 0}, IM_VEC2) end
function ImVec2(x, y) return setmetatable({ x = x or 0, y = y or 0 }, IM_VEC2) end
function IM_VEC2.__add(lhs, rhs) return ImVec2(lhs[1] + rhs[1], lhs[2] + rhs[2]) end
function IM_VEC2.__sub(lhs, rhs) return ImVec2(lhs[1] - rhs[1], lhs[2] - rhs[2]) end
function IM_VEC2.__add(lhs, rhs) return setmetatable({ x = lhs.x + rhs.x, y = lhs.y + rhs.y }, IM_VEC2) end
function IM_VEC2.__sub(lhs, rhs) return setmetatable({ x = lhs.x - rhs.x, y = lhs.y - rhs.y }, IM_VEC2) end
--- @overload fun(lhs: ImVec2, rhs: number): ImVec2
--- @overload fun(lhs: ImVec2, rhs: ImVec2): ImVec2
function IM_VEC2.__mul(lhs, rhs)
if type(lhs) == "table" and type(rhs) == "number" then return ImVec2(lhs[1] * rhs, lhs[2] * rhs)
elseif type(lhs) == "table" and type(rhs) == "table" then return ImVec2(lhs[1] * rhs[1], lhs[2] * rhs[2])
end
if type(rhs) == "number" then return setmetatable({ x = lhs.x * rhs, y = lhs.y * rhs }, IM_VEC2) end
return setmetatable({ x = lhs.x * rhs.x, y = lhs.y * rhs.y }, IM_VEC2)
end
--- @overload fun(lhs: ImVec2, rhs: number): ImVec2
--- @overload fun(lhs: ImVec2, rhs: ImVec2): ImVec2
function IM_VEC2.__div(lhs, rhs)
if type(lhs) == "table" and type(rhs) == "number" then return ImVec2(lhs[1] / rhs, lhs[2] / rhs)
elseif type(lhs) == "table" and type(rhs) == "table" then return ImVec2(lhs[1] / rhs[1], lhs[2] / rhs[2])
end
if type(rhs) == "number" then return setmetatable({ x = lhs.x / rhs, y = lhs.y / rhs }, IM_VEC2) end
return setmetatable({ x = lhs.x / rhs.x, y = lhs.y / rhs.y }, IM_VEC2)
end
function IM_VEC2.__eq(lhs, rhs) return lhs[1] == rhs[1] and lhs[2] == rhs[2] end
function IM_VEC2.__eq(lhs, rhs) return lhs.x == rhs.x and lhs.y == rhs.y end
function IM_VEC2:__tostring() return string.format("ImVec2(%g, %g)", self.x, self.y) end
--- @param dest ImVec2
--- @param src ImVec2
function ImVec2_Copy(dest, src) dest[1] = src[1]; dest[2] = src[2] end
function ImVec2_Copy(dest, src) dest.x = src.x; dest.y = src.y end
--- @param dest ImVec2
--- @param src_x number
--- @param src_y number
function ImVec2_CopyV(dest, src_x, src_y) dest[1] = src_x; dest[2] = src_y end
function ImVec2_CopyV(dest, src_x, src_y) dest.x = src_x; dest.y = src_y end
--- @param v ImVec2
--- @param add_x number
--- @param add_y number
function ImVec2_AddVA(v, add_x, add_y) return v[1] + add_x, v[2] + add_y end
function ImVec2_AddVA(v, add_x, add_y) return v.x + add_x, v.y + add_y end
--- @param v ImVec2
--- @param sub_x number
--- @param sub_y number
function ImVec2_SubVA(v, sub_x, sub_y) return v[1] - sub_x, v[2] - sub_y end
function ImVec2_SubVA(v, sub_x, sub_y) return v.x - sub_x, v.y - sub_y end
--- @param a ImVec2
--- @param scalar number
function ImVec2_MulVX(a, scalar)
return a[1] * scalar, a[2] * scalar
end
function ImVec2_MulVX(a, scalar) return a.x * scalar, a.y * scalar end
--- An inlined version of `ImVec2_Copy` currently for use in certain ImVector<ImVec2> `push_back`
--- @param t ImVec2[]
--- @param k int
--- @param v ImVec2
local function ImVec2_TCopy(t, k, v) local dest = t[k]; dest[1] = v[1]; dest[2] = v[2]; end
local function ImVec2_TCopy(t, k, v) local dest = t[k]; dest.x = v.x; dest.y = v.y; end
-- This structure supports indexing on string keys `x`, `y`, `z`, `w` and number keys 1, 2, 3, 4.
-- But note that the former is likely to be more expensive.
-- [Luau] Same layout as ImVec2: named fields x, y, z, w; numeric keys 1..4 go through the metatable.
--- @class ImVec4
--- @operator add(ImVec4): ImVec4
--- @operator sub(ImVec4): ImVec4
--- @operator mul(number): ImVec4
--- @field [1] number
--- @field [2] number
--- @field [3] number
--- @field [4] number
--- @field x number
--- @field y number
--- @field z number
--- @field w number
local IM_VEC4 = {}
local VEC4_KEYS = { "x", "y", "z", "w" }
--- @param t ImVec4
--- @param k string
IM_VEC4.__index = function(t, k)
if k == "x" then return rawget(t, 1)
elseif k == "y" then return rawget(t, 2)
elseif k == "z" then return rawget(t, 3)
elseif k == "w" then return rawget(t, 4)
end
local n = VEC4_KEYS[k]
if n then return rawget(t, n) end
end
--- @param t ImVec4
--- @param k string
--- @param v number
IM_VEC4.__newindex = function(t, k, v)
if k == "x" then rawset(t, 1, v)
elseif k == "y" then rawset(t, 2, v)
elseif k == "z" then rawset(t, 3, v)
elseif k == "w" then rawset(t, 4, v)
else IM_ASSERT(false)
end
local n = VEC4_KEYS[k]
IM_ASSERT(n ~= nil)
rawset(t, n, v)
end
--- @param x? number
--- @param y? number
--- @param z? number
--- @param w? number
--- @return ImVec4
--- @nodiscard
function ImVec4(x, y, z, w) return setmetatable({x or 0, y or 0, z or 0, w or 0}, IM_VEC4) end
function ImVec4(x, y, z, w) return setmetatable({ x = x or 0, y = y or 0, z = z or 0, w = w or 0 }, IM_VEC4) end
function IM_VEC4.__add(lhs, rhs) return ImVec4(lhs[1] + rhs[1], lhs[2] + rhs[2], lhs[3] + rhs[3], lhs[4] + rhs[4]) end
function IM_VEC4.__sub(lhs, rhs) return ImVec4(lhs[1] - rhs[1], lhs[2] - rhs[2], lhs[3] - rhs[3], lhs[4] - rhs[4]) end
function IM_VEC4.__mul(lhs, rhs) return ImVec4(lhs[1] * rhs, lhs[2] * rhs, lhs[3] * rhs, lhs[4] * rhs) end
function IM_VEC4.__eq(lhs, rhs) return lhs[1] == rhs[1] and lhs[2] == rhs[2] and lhs[3] == rhs[3] and lhs[4] == rhs[4] end
function IM_VEC4.__add(lhs, rhs) return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w) end
function IM_VEC4.__sub(lhs, rhs) return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w) end
function IM_VEC4.__mul(lhs, rhs) return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs) end
function IM_VEC4.__eq(lhs, rhs) return lhs.x == rhs.x and lhs.y == rhs.y and lhs.z == rhs.z and lhs.w == rhs.w end
function IM_VEC4:__tostring() return string.format("ImVec4(%g, %g, %g, %g)", self.x, self.y, self.z, self.w) end
--- @param dest ImVec4
--- @param src ImVec4
function ImVec4_Copy(dest, src) dest[1] = src[1]; dest[2] = src[2]; dest[3] = src[3]; dest[4] = src[4] end
function ImVec4_Copy(dest, src) dest.x = src.x; dest.y = src.y; dest.z = src.z; dest.w = src.w end
--- A compact ImVector clone
--- @class ImVector<T>
@@ -356,8 +307,10 @@ local IM_VECTOR = {}
--- @param k string|int
--- @return any
IM_VECTOR.__index = function(t, k)
local m = IM_VECTOR[k] -- methods first (hot path)
if m ~= nil then return m end
if k == "Data" then return nil end -- Data has already been discarded
return IM_VECTOR[k] or t.Data[IM_ASSERT(k >= 1 and k <= t.Size) or k] -- if the mt access turns out nil, the k must be int index into Data
return t.Data[IM_ASSERT(k >= 1 and k <= t.Size) or k] -- if the mt access turns out nil, the k must be int index into Data
end
--- @param t ImVector