-- Minimal Roblox mock for running the imgui Roblox backend + the real CanvasDraw module in the Luau CLI. local realG = {} -- (no getfenv: it would mark the global env unsafe and deoptimize everything) FAKE_TIME = 0 local SCREEN_W, SCREEN_H = 1920, 1080 local NFRAMES = tonumber(ARG_FRAMES) or 400 local FORCE = ARG_FORCE == true local DUMP_FRAMES = { [60] = true, [200] = true, [NFRAMES] = true } local function Signal() local s = { conns = {} } function s:Connect(fn) local c = { fn = fn, Connected = true } function c:Disconnect() c.Connected = false end table.insert(s.conns, c) return c end function s:Fire(...) for _, c in ipairs(s.conns) do if c.Connected then c.fn(...) end end end return s end local V2 = {} V2.__index = V2 local function v2(x, y) return setmetatable({ X = x or 0, Y = y or 0 }, V2) end V2.__add = function(a, b) return v2(a.X + b.X, a.Y + b.Y) end V2.__sub = function(a, b) return v2(a.X - b.X, a.Y - b.Y) end V2.__mul = function(a, b) if type(b) == "number" then return v2(a.X * b, a.Y * b) end return v2(a.X * b.X, a.Y * b.Y) end V2.__div = function(a, b) if type(b) == "number" then return v2(a.X / b, a.Y / b) end return v2(a.X / b.X, a.Y / b.Y) end V2.__eq = function(a, b) return a.X == b.X and a.Y == b.Y end local Vector2 = { new = v2, zero = v2(0, 0), one = v2(1, 1) } -- Enums: KeyCode values as in Roblox; unknown KeyCode names error (catches typos in the backend) local KEYCODES = { Unknown = 0, Backspace = 8, Tab = 9, Return = 13, Escape = 27, Space = 32, Quote = 39, Comma = 44, Minus = 45, Period = 46, Slash = 47, Semicolon = 59, Equals = 61, LeftBracket = 91, BackSlash = 92, RightBracket = 93, Backquote = 96, Delete = 127, KeypadPeriod = 266, KeypadDivide = 267, KeypadMultiply = 268, KeypadMinus = 269, KeypadPlus = 270, KeypadEnter = 271, KeypadEquals = 272, Up = 273, Down = 274, Right = 275, Left = 276, Insert = 277, Home = 278, End = 279, PageUp = 280, PageDown = 281, CapsLock = 301, RightShift = 303, LeftShift = 304, RightControl = 305, LeftControl = 306, RightAlt = 307, LeftAlt = 308, LeftSuper = 311, RightSuper = 312, } local digits = { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" } for i = 0, 9 do KEYCODES[digits[i + 1]] = 48 + i; KEYCODES["Keypad" .. digits[i + 1]] = 256 + i end for i = 0, 25 do KEYCODES[string.char(65 + i)] = 97 + i end for i = 1, 15 do KEYCODES["F" .. i] = 281 + i end local Enum = setmetatable({}, { __index = function(t, enum_name) local e = setmetatable({}, { __index = function(e, item) local value = 0 if enum_name == "KeyCode" then value = KEYCODES[item]; assert(value, "invalid KeyCode " .. item) end local it = { Name = item, Value = value, EnumType = enum_name } rawset(e, item, it) return it end }) rawset(t, enum_name, e) return e end }) -- Instances local InstMethods = {} local InstMT = {} InstMT.__index = function(t, k) if k == "Parent" then return rawget(t, "_parent") end local m = InstMethods[k]; if m then return m end for _, c in ipairs(rawget(t, "_children")) do if c.Name == k then return c end end return nil end InstMT.__newindex = function(t, k, v) if k == "Parent" then local old = rawget(t, "_parent") if old then local ch = rawget(old, "_children"); for i, c in ipairs(ch) do if c == t then table.remove(ch, i) break end end end rawset(t, "_parent", v) if v then table.insert(rawget(v, "_children"), t) end else rawset(t, k, v) end end local function newInst(class, name, parent) local o = setmetatable({ ClassName = class, Name = name or class, _children = {} }, InstMT) if class == "ScreenGui" or class == "Frame" or class == "ImageLabel" then o.AbsoluteSize = v2(SCREEN_W, SCREEN_H) end o.Visible = true if class == "BindableEvent" then local sig = Signal(); rawset(o, "Event", sig); rawset(o, "Fire", function(_, ...) sig:Fire(...) end) end if parent then o.Parent = parent end return o end function InstMethods:FindFirstChild(name, recursive) for _, c in ipairs(rawget(self, "_children")) do if c.Name == name then return c end end if recursive then for _, c in ipairs(rawget(self, "_children")) do local r = c:FindFirstChild(name, true); if r then return r end end end return nil end function InstMethods:WaitForChild(name) return assert(self:FindFirstChild(name), "WaitForChild would hang: " .. name) end function InstMethods:FindFirstChildOfClass(class) for _, c in ipairs(rawget(self, "_children")) do if c.ClassName == class then return c end end end function InstMethods:GetChildren() return table.clone(rawget(self, "_children")) end function InstMethods:IsA(class) if class == "GuiObject" then return self.ClassName == "Frame" or self.ClassName == "ImageLabel" end return self.ClassName == class end function InstMethods:Destroy() self.Parent = nil end -- Services local mouse = v2(0, 0) local keys_down = {} local UIS = { InputBegan = Signal(), InputEnded = Signal(), InputChanged = Signal(), WindowFocused = Signal(), WindowFocusReleased = Signal(), } function UIS:GetMouseLocation() return mouse end function UIS:IsKeyDown(kc) return keys_down[kc] == true end local RunService = { Heartbeat = Signal(), Stepped = Signal(), PreAnimation = Signal(), RenderStepped = Signal() } local last_pixels -- last buffer written to an EditableImage local AssetService = {} function AssetService:CreateEditableImage(opts) local img = { Size = opts.Size, Pixels = buffer.create(opts.Size.X * opts.Size.Y * 4) } UPLOADS = 0 function img:WritePixelsBuffer(origin, size, buf) buffer.copy(img.Pixels, 0, buf); _G_UPLOADS = (_G_UPLOADS or 0) + 1 end function img:Destroy() end return img end local ReplicatedStorage = newInst("ReplicatedStorage") local PlayerGui = newInst("PlayerGui") local LocalPlayer = newInst("Player"); PlayerGui.Parent = LocalPlayer local services = { UserInputService = UIS, RunService = RunService, AssetService = AssetService, ReplicatedStorage = ReplicatedStorage, Players = { LocalPlayer = LocalPlayer }, HttpService = {}, GuiService = { GetGuiInset = function() return v2(0, 0), v2(0, 0) end }, } local game = {} function game:GetService(name) services[name] = services[name] or {}; return services[name] end local fakeG = {} -- Roblox's shared _G local cache = {} local ROBLOX local function makeEnv(inst) return setmetatable({ script = inst }, { __index = ROBLOX }) end local GLOBAL_NAMES = { "game", "workspace", "Instance", "Enum", "Vector2", "Vector3", "UDim2", "Color3", "Content", "warn", "require", "script", "os" } -- Load like Roblox does but WITHOUT setfenv (which would disable Luau optimizations): Roblox globals become chunk locals. local function loadAsScript(src_name, inst) local fn = SOURCE_FNS[src_name] local vals = {} for i, n in ipairs(GLOBAL_NAMES) do vals[i] = (n == "script") and inst or ROBLOX[n] end return function() return fn(table.unpack(vals, 1, #GLOBAL_NAMES)) end end local function rbxrequire(inst) assert(type(inst) == "table" and inst.ClassName == "ModuleScript", "require() expects a ModuleScript") if cache[inst] then return cache[inst].v end local r = table.pack(loadAsScript(rawget(inst, "_src"), inst)()) assert(r.n == 1, "Module code did not return exactly one value") cache[inst] = { v = r[1] } return r[1] end ROBLOX = setmetatable({ game = game, workspace = { CurrentCamera = { ViewportSize = v2(SCREEN_W, SCREEN_H) } }, require = rbxrequire, _G = fakeG, Instance = { new = function(class) return newInst(class) end }, Enum = Enum, Vector2 = Vector2, Vector3 = { new = function(x, y, z) return { X = x, Y = y, Z = z } end }, UDim2 = { new = function(...) return { ... } end, fromScale = function(...) return { ... } end, fromOffset = function(x, y) return { X = x, Y = y } end }, Color3 = { new = function(r, g, b) return { R = r or 0, G = g or 0, B = b or 0 } end }, Content = { fromObject = function(o) return { Object = o } end }, warn = function(...) print("WARN", ...) end, os = { clock = function() return FAKE_TIME end, time = os.time }, }, { __index = realG }) local mod = newInst("ModuleScript", "ImGui", ReplicatedStorage); rawset(mod, "_src", "ImGui.luau") -- Run main.lua as a LocalScript local main = loadAsScript("main.client.luau", newInst("LocalScript", "main")) main() local G = cache[mod].v local render_time = 0 local realDemo = G.ImGui.ShowDemoWindow local realCH = G.ImGui.CollapsingHeader G.ImGui.CollapsingHeader = function(...) if ARG_OPEN then G.ImGui.SetNextItemOpen(true, G.ImGuiCond.Always) end return realCH(...) end local realTN = G.ImGui.TreeNode G.ImGui.TreeNode = function(...) if ARG_OPEN then G.ImGui.SetNextItemOpen(true, G.ImGuiCond.Always) end return realTN(...) end G.ImGui.ShowDemoWindow = function(...) if ARG_BIG then G.ImGui.SetNextWindowSize(G.ImVec2(900, 1000), G.ImGuiCond.Always) end return realDemo(...) end local realRender = G.ImGui_ImplRoblox.RenderDrawData G.ImGui_ImplRoblox.RenderDrawData = function(dd) if FORCE then G.ImGui.GetIO().BackendPlatformUserData.ForceRedraw = true end local t0 = os.clock(); realRender(dd); render_time = render_time + os.clock() - t0 end -- Drive frames with scripted (deterministic) input local seed = 12345 local function rnd(n) seed = (seed * 1103515245 + 12345) % 2147483648; return seed % n end local function input(t, kc, pos) return { UserInputType = Enum.UserInputType[t], KeyCode = Enum.KeyCode[kc or "Unknown"], Position = pos or { X = 0, Y = 0, Z = 0 } } end local held = false local typing = { "H", "E", "L", "L", "O", "Space", "One", "Two", "Minus", "Backspace", "Left", "Return" } local hex = {} for i = 0, 255 do hex[i] = string.format("%02x", i) end local total = 0 for frame = 1, NFRAMES do local r = ARG_QUIET and 100 or rnd(100) if r < 40 then mouse = v2(math.clamp(mouse.X + rnd(81) - 40, 0, SCREEN_W - 1), math.clamp(mouse.Y + rnd(81) - 40, 0, SCREEN_H - 1)) elseif r < 55 then held = not held if held then UIS.InputBegan:Fire(input("MouseButton1"), false) else UIS.InputEnded:Fire(input("MouseButton1"), false) end elseif r < 60 then UIS.InputChanged:Fire(input("MouseWheel", nil, { X = 0, Y = 0, Z = rnd(3) - 1 }), false) elseif r < 75 then local k = typing[rnd(#typing) + 1] local shift = rnd(4) == 0 if shift then keys_down[Enum.KeyCode.LeftShift] = true; UIS.InputBegan:Fire(input("Keyboard", "LeftShift"), false) end UIS.InputBegan:Fire(input("Keyboard", k), false) UIS.InputEnded:Fire(input("Keyboard", k), false) if shift then keys_down[Enum.KeyCode.LeftShift] = nil; UIS.InputEnded:Fire(input("Keyboard", "LeftShift"), false) end elseif r < 90 then mouse = v2(rnd(700), rnd(500)) end FAKE_TIME = frame / 60 local t0 = os.clock() RunService.Stepped:Fire(frame / 60, 1 / 60) local dt = os.clock() - t0 if frame > 10 then total = total + dt end if frame == 10 then render_time = 0; _G_UPLOADS = 0 end end print(string.format("avg frame total %.2f ms, of which render %.2f ms, uploads/frame %.2f", total / (NFRAMES - 10) * 1000, render_time / (NFRAMES - 10) * 1000, _G_UPLOADS / (NFRAMES - 10)))