Reorganize like upstream: library at root (docking/dragdrop/debug tools merged into imgui.lua, tabs into imgui_widgets.lua), single imgui_demo.lua, backends/, fonts/, examples/, docs/; --no-demo build
This commit is contained in:
+11
-8
@@ -1,26 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
# Bundles imgui/*.lua + backend into ONE ModuleScript: build/ImGui.luau
|
||||
# Bundles the root *.lua (library + imgui_demo.lua), backends/ and fonts/ into ONE ModuleScript: build/ImGui.luau
|
||||
# --no-demo: library only (imgui_demo.lua left out, like not compiling imgui_demo.cpp)
|
||||
# Why: ModuleScripts don't share globals, and the setfenv() alternative turns off Luau's fast paths / native codegen.
|
||||
# Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result.
|
||||
# The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...).
|
||||
import os, re
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SRC = os.path.join(ROOT, "imgui")
|
||||
import sys
|
||||
NO_DEMO = "--no-demo" in sys.argv
|
||||
sys.argv = [a for a in sys.argv if a != "--no-demo"]
|
||||
FILES = {}
|
||||
for dp, _, fn in os.walk(SRC):
|
||||
for f in sorted(fn):
|
||||
if f.endswith(".lua"):
|
||||
FILES[f] = open(os.path.join(dp, f)).read()
|
||||
for d in (ROOT, os.path.join(ROOT, "backends"), os.path.join(ROOT, "fonts")):
|
||||
for f in sorted(os.listdir(d)):
|
||||
if f.endswith(".lua") and not (NO_DEMO and f == "imgui_demo.lua"):
|
||||
FILES[f] = open(os.path.join(d, f)).read()
|
||||
|
||||
ENTRY = ["imgui.lua", "imgui_impl_roblox.lua"] + sorted(f for f in FILES if f.startswith("imgui_demo")) # run order
|
||||
ENTRY = ["imgui.lua", "imgui_impl_roblox.lua"] + ([] if NO_DEMO else ["imgui_demo.lua"]) # run order
|
||||
|
||||
names = set()
|
||||
for src in FILES.values():
|
||||
names.update(re.findall(r"^function ([A-Za-z_]\w*)\s*\(", src, re.M))
|
||||
names.update(re.findall(r"^([A-Za-z_]\w*)\s*=[^=]", src, re.M))
|
||||
|
||||
out = ["--!optimize 2", "-- GENERATED by tools/bundle.py from imgui/*.lua - do not edit", "",
|
||||
out = ["--!optimize 2", "-- GENERATED by tools/bundle.py from *.lua, backends/, fonts/ - do not edit", "",
|
||||
"local __FILES, __LOADED = {}, {}",
|
||||
"function IM_INCLUDE(name)",
|
||||
" local r = __LOADED[name]",
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# 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.
|
||||
- Each `imgui/*.lua` file is a function body in the bundle. New files must be loaded with `IM_INCLUDE"file.lua"`
|
||||
from an existing file (files named `imgui_demo*.lua` are loaded automatically, after `imgui.lua`).
|
||||
- No `goto`, no `::labels::`, no `setfenv/getfenv`, no LuaJIT `bit`/`ffi`. Use `bit32`. For `continue` use
|
||||
`repeat ... until true` with `break`, 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.
|
||||
|
||||
## Conventions (follow what the port already does)
|
||||
- `ImVector`: 1-based `.Data`, `.Size`, methods `push_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-based` next to anything ambiguous.
|
||||
- `ImVec2(x, y)` stores `[1],[2]` with `.x/.y` aliases (metatable). Arithmetic operators exist. Prefer
|
||||
`ImVec2_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 on `MT.ImGuiFoo` with
|
||||
`MT.ImGuiFoo.__index = MT.ImGuiFoo`. `local MT = ImGui.GetMetatables()`.
|
||||
- Flags/enums: tables like `ImGuiTableFlags.Resizable`; combine with `bit32.bor`, test with `bit32.band(...) ~= 0`.
|
||||
- Context: `local g = GImGui` (each file has its own local `GImGui` kept in sync; follow how the file you are in
|
||||
does it). New fields on `ImGuiContext`/`ImGuiWindow`: add them to the constructor in imgui_internal.lua.
|
||||
- Strings: Lua strings + 1-based byte positions; `text_end` is exclusive. Editable text buffers are `char[]`
|
||||
(byte tables), like `ImGui.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, return `changed`
|
||||
- `Begin(name, p_open)` style: `return open, visible` / follow existing `ImGui.Begin`.
|
||||
When an existing similar function already exists, copy its convention exactly.
|
||||
- `static` locals in C++ become file-level upvalue tables / fields (demo uses a `static` table per section).
|
||||
- `IM_ASSERT(cond, msg)` exists. `IM_COL32(r,g,b,a)`, `ImMax/ImMin/ImClamp/ImLerp/ImFloor/ImTrunc` etc. exist.
|
||||
- Keep function names identical to upstream (`ImGui.TableNextRow`, `ImGui.TabBarLayout`, ...). Internal helpers
|
||||
that are `static` in C++ become `local function` in the file.
|
||||
- Roblox can't do: OS clipboard (internal only), files/URLs (VFS `ImStd.ImFileLoadToMemory` only), 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 main.client.luau 400 frames with scripted random input (mouse, clicks,
|
||||
keys, typing). Errors print with `imgui/<file>.lua:<line>` mapped stack traces.
|
||||
- `--open` forces every CollapsingHeader/TreeNode open (exercises the whole demo). `--big` makes the demo
|
||||
incremental renderer is pixel identical to a full redraw. `--main file.luau` runs your own test LocalScript
|
||||
(copy main.client.luau as a template: `local G = require(ReplicatedStorage:WaitForChild("ImGui"))`, all
|
||||
library globals are fields of `G`).
|
||||
- `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`; upstream `None=-1, X=0, Y=1`). Use `v[axis]` to index an ImVec2 and `3 - axis` for the other axis. `ImGuiDir` keeps upstream values (`None=-1, Left=0, Right=1, Up=2, Down=3`).
|
||||
- Never write `cond and nil or x` in Lua: it always evaluates `x`. Use an `if`.
|
||||
- Port signature quirk: `AddRect(min, max, col, rounding, thickness, flags)` (thickness BEFORE flags; upstream has flags first). Check the port's signature before porting any draw call that takes both.\n
|
||||
- C++ default arguments (e.g. `bool x = true`) must be reproduced with `if x == nil then x = true end`; a missing Lua argument is nil/falsy.
|
||||
@@ -7,9 +7,9 @@ import os, re, sys
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(os.path.dirname(HERE))
|
||||
port = set()
|
||||
for f in os.listdir(os.path.join(ROOT, "imgui")):
|
||||
for f in os.listdir(ROOT):
|
||||
if f.endswith(".lua"):
|
||||
port.update(re.findall(r"function ImGui[.:](\w+)", open(os.path.join(ROOT, "imgui", f)).read()))
|
||||
port.update(re.findall(r"function ImGui[.:](\w+)", open(os.path.join(ROOT, f)).read()))
|
||||
filt = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
for f in sorted(os.listdir(os.path.join(HERE, "upstream"))):
|
||||
if not f.endswith(".cpp") or f == "imgui_demo.cpp": continue
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# Summarise profile.out from `run.py --profile`: top functions by self and inclusive samples, mapped to imgui/*.lua lines
|
||||
# Summarise profile.out from `run.py --profile`: top functions by self and inclusive samples, mapped to source file lines
|
||||
import collections, json, os, re, sys, glob
|
||||
path = sys.argv[1] if len(sys.argv) > 1 else "profile.out"
|
||||
tmp = max(glob.glob("/tmp/imgui_test_*/ImGui.linemap.json"), key=os.path.getmtime)
|
||||
|
||||
@@ -180,10 +180,10 @@ G.ImGui.TreeNode = function(...)
|
||||
if ARG_OPEN == true or (type(ARG_OPEN) == "table" and ARG_OPEN[(...)]) then G.ImGui.SetNextItemOpen(true, G.ImGuiCond.Always) end
|
||||
return realTN(...)
|
||||
end
|
||||
G.ImGui.ShowDemoWindow = function(...)
|
||||
if realDemo then G.ImGui.ShowDemoWindow = function(...)
|
||||
if ARG_BIG then G.ImGui.SetNextWindowSize(G.ImVec2(900, 1000), G.ImGuiCond.Always) end
|
||||
return realDemo(...)
|
||||
end
|
||||
end end
|
||||
local realRender = G.ImGui_ImplRoblox.RenderDrawData
|
||||
G.ImGui_ImplRoblox.RenderDrawData = function(dd)
|
||||
if FORCE then G.ImGui.GetIO().BackendPlatformUserData.ForceRedraw = true end
|
||||
|
||||
-36490
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -2,7 +2,7 @@
|
||||
"""Headless test harness: runs build/ImGui.luau + a main script in the Luau CLI against a mocked Roblox
|
||||
(roblox_mock.luau). Rebuilds the bundle first.
|
||||
|
||||
python3 tools/test/run.py # 400 frames of main.client.luau, scripted random input
|
||||
python3 tools/test/run.py # 400 frames of examples/example_roblox/main.client.luau, scripted random input
|
||||
python3 tools/test/run.py --open # force every CollapsingHeader/TreeNode open (exercises whole demo)
|
||||
python3 tools/test/run.py --main my_test.luau # different LocalScript
|
||||
"""
|
||||
@@ -15,7 +15,7 @@ LUAU = shutil.which("luau") or "/nix/store/2m2ja613nndihc8qm10f2rhq6znp1nh4-luau
|
||||
G = "game, workspace, Instance, Enum, Vector2, Vector3, UDim2, Color3, Content, warn, require, script, os"
|
||||
|
||||
def build_run(args, flags):
|
||||
subprocess.run([sys.executable, os.path.join(ROOT, "tools", "bundle.py"), TMP], check=True, stdout=subprocess.DEVNULL)
|
||||
subprocess.run([sys.executable, os.path.join(ROOT, "tools", "bundle.py"), TMP] + (["--no-demo"] if os.environ.get("NO_DEMO") else []), check=True, stdout=subprocess.DEVNULL)
|
||||
out = ["--" if args.roblox else "--!native", "--!optimize 2", flags, "local SOURCE_FNS = {}"]
|
||||
def add(key, path):
|
||||
s = "\n".join(l for l in open(path).read().split("\n") if not l.startswith("--!"))
|
||||
@@ -39,7 +39,7 @@ def run(args, extra=""):
|
||||
return p.returncode, remap(p.stdout + p.stderr)
|
||||
|
||||
def remap(text):
|
||||
"""run.luau:N -> imgui/<file>.lua:M"""
|
||||
"""run.luau:N -> <file>.lua:M"""
|
||||
import json, re
|
||||
lm = json.load(open(os.path.join(TMP, "ImGui.linemap.json")))
|
||||
def sub(m):
|
||||
@@ -56,7 +56,7 @@ def remap(text):
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--frames", type=int, default=400)
|
||||
ap.add_argument("--main", default=os.path.join(ROOT, "main.client.luau"))
|
||||
ap.add_argument("--main", default=os.path.join(ROOT, "examples", "example_roblox", "main.client.luau"))
|
||||
ap.add_argument("--big", action="store_true", help="demo window forced to 900x1000")
|
||||
ap.add_argument("--roblox", action="store_true", help="like Studio: only @native functions get native code (default: whole bundle native)")
|
||||
ap.add_argument("--open-only", default="", help="comma list of CollapsingHeader/TreeNode labels to force open, e.g. Widgets,Basic")
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Library without imgui_demo.lua: run with NO_DEMO=1 (bundle --no-demo). Window + Metrics/Debug Log/ID Stack tools must work.
|
||||
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||
local ImGui = G.ImGui
|
||||
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
|
||||
assert(ImGui.ShowDemoWindow == nil, "demo was bundled")
|
||||
local f = 0
|
||||
game:GetService("RunService").Stepped:Connect(function()
|
||||
f += 1
|
||||
G.ImGui_ImplRoblox.NewFrame(); ImGui.NewFrame()
|
||||
ImGui.Begin("W"); ImGui.Text("hi"); ImGui.Button("b"); ImGui.End()
|
||||
ImGui.ShowMetricsWindow(); ImGui.ShowDebugLogWindow(); ImGui.ShowIDStackToolWindow()
|
||||
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||
if f == 20 then print("NODEMO OK") end
|
||||
end)
|
||||
@@ -1,14 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Static check: ImGui.X / ImGui:X / global ImFoo() calls in imgui/*.lua that nothing defines.
|
||||
"""Static check: ImGui.X / ImGui:X / global ImFoo() calls in the library *.lua files that nothing defines.
|
||||
python3 tools/test/undefined.py
|
||||
"""
|
||||
import os, re, collections
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
SRC = os.path.join(ROOT, "imgui")
|
||||
SRC = ROOT # library *.lua at the root + backends/ (--no-demo: leave imgui_demo.lua out to check the library never needs it)
|
||||
import sys
|
||||
NO_DEMO = "--no-demo" in sys.argv
|
||||
files = {}
|
||||
for dp, _, fn in os.walk(SRC):
|
||||
for dp, fn in ((ROOT, os.listdir(ROOT)), (os.path.join(ROOT, "backends"), os.listdir(os.path.join(ROOT, "backends")))):
|
||||
for f in fn:
|
||||
if f.endswith(".lua"):
|
||||
if f.endswith(".lua") and not (NO_DEMO and f == "imgui_demo.lua"):
|
||||
files[os.path.relpath(os.path.join(dp, f), SRC)] = open(os.path.join(dp, f)).read()
|
||||
|
||||
def strip(s): # drop comments and strings (roughly) so we don't report text
|
||||
|
||||
Reference in New Issue
Block a user