6.2 KiB
Porting rules (Dear ImGui docking branch -> this Lua/Luau port)
Upstream reference: tools/test/upstream/*.cpp|h (ocornut/imgui docking branch, 1.93 WIP). Port semantics 1:1 from
there, but keep this port's existing data structures and conventions. Read the surrounding existing Lua code first.
Runtime
- Target is Luau (Roblox) only. All files are concatenated into one ModuleScript by
tools/bundle.py; every file shares one global namespace. Globals defined by one file are visible to the others at call time. - Layout mirrors upstream: library at the repo root (
imgui.luaincl. docking, drag & drop and the debug tools,imgui_widgets.luaincl. tab bars,imgui_draw.lua,imgui_tables.lua,imgui_internal.lua,imgui_h.lua,imstb_*.lua), demo inimgui_demo.luaonly, backend inbackends/, fonts infonts/, the example LocalScript inexamples/example_roblox/. The library must never call intoimgui_demo.lua(tools/bundle.py --no-demo,tools/test/undefined.py --no-demo,NO_DEMO=1 run.py --main tools/test/scripts/t_nodemo.luau). - Each
.luafile is a function body in the bundle. New files must be loaded withIM_INCLUDE"file.lua"from an existing file. Merged-in sections are wrapped indo ... endso their locals stay private. - No
goto, no::labels::, nosetfenv/getfenv, no LuaJITbit/ffi. Usebit32. Forcontinueuserepeat ... until truewithbreak, and an explicit flag when the loop itself must break out of the wrapper. - Luau has a limit of 200 locals per function and 255 upvalues; split huge functions (like demo sections) into several local/global functions.
Portability (LÖVE build)
- Library code must stay valid Lua 5.1/LuaJIT syntax apart from
@nativelines (stripped for LÖVE): no+=,//,continue, type annotations,if-expressions or string interpolation. Luau library calls are fine only if the COMPAT section ofbackends/imgui_impl_love.luashims them (bit32,buffer,table.clear/find/move/unpack,utf8). - Check with
tools/test/love/run.sh(LuaJIT draw checksums must equal the Luau ones).
Conventions (follow what the port already does)
ImVector: 1-based.Data,.Size, methodspush_back/pop_back/resize/back/iter/erase/...(imgui_h.lua). Indices that C++ stores as 0-based values (e.g. column index, tab order) stay 0-based unless the surrounding port code already shifted them; document-- 0-based/-- 1-basednext to anything ambiguous.ImVec2(x, y)stores named fields.x/.y([1],[2]go through the metatable, slower). Arithmetic operators exist. PreferImVec2_Copy(dst, src)/ImVec2_CopyV(dst, x, y)when C++ assigns into an existing struct field.ImVec4,ImRect(.Min/.Max) likewise.- Structs: constructor function
ImGuiFoo()returning a table, methods onMT.ImGuiFoowithMT.ImGuiFoo.__index = MT.ImGuiFoo.local MT = ImGui.GetMetatables(). - Flags/enums: tables like
ImGuiTableFlags.Resizable; combine withbit32.bor, test withbit32.band(...) ~= 0. - Context:
local g = GImGui(each file has its own localGImGuikept in sync; follow how the file you are in does it). New fields onImGuiContext/ImGuiWindow: add them to the constructor in imgui_internal.lua. - Strings: Lua strings + 1-based byte positions;
text_endis exclusive. Editable text buffers arechar[](byte tables), likeImGui.InputText(label, buf, buf_size). - Output parameters (C++
bool* p_open,float* v,int* current_item...) become return values:- bool-like widgets:
return pressed, new_v(Checkbox, RadioButton, Selectable, MenuItem, CheckboxFlags) - scalar widgets:
return new_v, changed(Slider*/Drag*/Input* scalar, SliderAngle, Combo ->new_idx, changed) - multi-component (
float v[3], ImVec4 colors,char[]buffers): table mutated in place, returnchanged Begin(name, p_open)style:return open, visible/ follow existingImGui.Begin. When an existing similar function already exists, copy its convention exactly.
- bool-like widgets:
staticlocals in C++ become file-level upvalue tables / fields (demo uses astatictable per section).IM_ASSERT(cond, msg)exists.IM_COL32(r,g,b,a),ImMax/ImMin/ImClamp/ImLerp/ImFloor/ImTruncetc. exist.- Keep function names identical to upstream (
ImGui.TableNextRow,ImGui.TabBarLayout, ...). Internal helpers that arestaticin C++ becomelocal functionin the file. - Roblox can't do: OS clipboard (internal only), files/URLs (VFS
ImStd.ImFileLoadToMemoryonly), multi-viewports, gamepad. Port the code paths anyway where cheap; demo shows "(not supported on Roblox)" text instead of widgets that cannot work.
Testing
-
python3 tools/test/run.py— runs examples/example_roblox/main.client.luau 400 frames with scripted random input (mouse, clicks, keys, typing). Errors print with<file>.lua:<line>mapped stack traces. -
--openforces every CollapsingHeader/TreeNode open (exercises the whole demo).--bigmakes the demo incremental renderer is pixel identical to a full redraw.--main file.luauruns your own test LocalScript (copy main.client.luau as a template:local G = require(ReplicatedStorage:WaitForChild("ImGui")), all library globals are fields ofG). -
python3 tools/test/missing.py [tables|widgets|imgui.cpp]lists upstream functions not yet ported. -
Several people edit the repo concurrently. Keep every file syntactically valid after each edit. If a run fails because of an error in a file you don't own, wait a minute and retry instead of editing it.
-
ImGuiAxis is 1-based in this port (
None=0, X=1, Y=2; upstreamNone=-1, X=0, Y=1). Usev[axis]to index an ImVec2 and3 - axisfor the other axis.ImGuiDirkeeps upstream values (None=-1, Left=0, Right=1, Up=2, Down=3). -
Never write
cond and nil or xin Lua: it always evaluatesx. Use anif. -
Draw calls taking both thickness and flags use upstream's 1.92.8+ order: thickness BEFORE flags, e.g.
AddRect(min, max, col, rounding, thickness, flags),PathStroke(col, thickness, flags),AddPolyline(points, count, col, thickness, flags). -
C++ default arguments (e.g.
bool x = true) must be reproduced withif x == nil then x = true end; a missing Lua argument is nil/falsy.