From 5dcda2b550a0705c2f66c4b80644031ba18c2153 Mon Sep 17 00:00:00 2001 From: 4DBug <4DBug@github.com> Date: Thu, 8 Oct 2026 18:54:25 -0500 Subject: [PATCH] Move LuaJIT compat shims into backends/imgui_impl_love.lua (COMPAT section); remove compat/ --- README.md | 4 +- backends/imgui_impl_love.lua | 140 +++++++++++++++++++++++++++++++++ build/imgui_love.linemap.json | 2 +- build/imgui_love.lua | 144 +++++++++++++++++++++++++++++++++- compat/luajit.lua | 137 -------------------------------- docs/PORTING.md | 2 +- tools/bundle.py | 4 +- 7 files changed, 288 insertions(+), 145 deletions(-) delete mode 100644 compat/luajit.lua diff --git a/README.md b/README.md index 17717e8..d6db4fe 100644 --- a/README.md +++ b/README.md @@ -172,11 +172,11 @@ io.Fonts:AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0) The same sources also build for [LÖVE](https://love2d.org) (LuaJIT), with a GPU renderer in [backends/imgui_impl_love.lua](backends/imgui_impl_love.lua): ``` -python3 tools/bundle.py --target love # -> build/imgui_love.lua (LÖVE backend + compat/luajit.lua shims) +python3 tools/bundle.py --target love # -> build/imgui_love.lua (LÖVE backend + shims from the LÖVE backend) love examples/example_love ``` -Roblox stays the primary target: the Roblox build is untouched by this (no shims, `@native` kept); the LÖVE build strips `@native` and adds small `bit32`/`buffer`/`utf8`/`table` shims ([compat/luajit.lua](compat/luajit.lua)). `tools/test/love/run.sh` checks that LuaJIT produces exactly the same draw data as Luau. +Roblox stays the primary target: the Roblox build is untouched by this (no shims, `@native` kept); the LÖVE build strips `@native` and adds small `bit32`/`buffer`/`utf8`/`table` shims (the COMPAT section of [backends/imgui_impl_love.lua](backends/imgui_impl_love.lua)). `tools/test/love/run.sh` checks that LuaJIT produces exactly the same draw data as Luau. [examples/example_shared/app.lua](examples/example_shared/app.lua) is one UI that runs unchanged on both: `roblox.client.luau` (Rojo: `examples/example_shared/default.project.json`) and `main.lua` (`love examples/example_shared`) are the only platform code. Write shared UI code in plain Lua 5.1 syntax (no `+=`, `//`, `continue`). diff --git a/backends/imgui_impl_love.lua b/backends/imgui_impl_love.lua index 83c91dc..ebca5f3 100644 --- a/backends/imgui_impl_love.lua +++ b/backends/imgui_impl_love.lua @@ -6,7 +6,147 @@ --- love.draw: ImGui_ImplLove.RenderDrawData(ImGui.GetDrawData()) --- forward love.mousemoved/mousepressed/mousereleased/wheelmoved/keypressed/keyreleased/textinput to ImGui_ImplLove.* +-- [SECTION] COMPAT (Luau stdlib for LuaJIT). tools/bundle.py --target love runs this section before the library. +--@compat-begin local ffi = require("ffi") +local floor = math.floor +-- Luau standard library pieces the port uses, for LuaJIT (LÖVE). Prepended by `tools/bundle.py --target love` only: +-- the Roblox build uses Luau's native bit32/buffer/table/utf8 and never sees this file. + +local bit = require("bit") +local TWO32 = 4294967296 + +-- bit32: unsigned 32-bit results like Luau/Lua 5.2 (LuaJIT's `bit` returns signed values) +if not bit32 then + local band, bor, bxor, bnot, lshift, rshift, arshift, tobit = bit.band, bit.bor, bit.bxor, bit.bnot, bit.lshift, bit.rshift, bit.arshift, bit.tobit + local function u(x) return x % TWO32 end + local function t(x) return tobit(x % TWO32) end -- any number -> int32 (bit.* wraps doubles >= 2^31 unreliably) + bit32 = {} + function bit32.band(a, b, ...) + if a == nil then return TWO32 - 1 end + if b == nil then return u(t(a)) end + local r = band(t(a), t(b)) + if ... ~= nil then for i = 1, select("#", ...) do r = band(r, t((select(i, ...)))) end end + return u(r) + end + function bit32.bor(a, b, ...) + if a == nil then return 0 end + if b == nil then return u(t(a)) end + local r = bor(t(a), t(b)) + if ... ~= nil then for i = 1, select("#", ...) do r = bor(r, t((select(i, ...)))) end end + return u(r) + end + function bit32.bxor(a, b, ...) + if a == nil then return 0 end + if b == nil then return u(t(a)) end + local r = bxor(t(a), t(b)) + if ... ~= nil then for i = 1, select("#", ...) do r = bxor(r, t((select(i, ...)))) end end + return u(r) + end + function bit32.bnot(a) return u(bnot(t(a))) end + function bit32.lshift(a, n) if n >= 32 or n <= -32 then return 0 end if n < 0 then return u(rshift(t(a), -n)) end return u(lshift(t(a), n)) end + function bit32.rshift(a, n) if n >= 32 or n <= -32 then return 0 end if n < 0 then return u(lshift(t(a), -n)) end return u(rshift(t(a), n)) end + function bit32.arshift(a, n) if n >= 32 then n = 31 end return u(arshift(t(a), n)) end + function bit32.btest(...) return bit32.band(...) ~= 0 end + function bit32.extract(n, field, width) width = width or 1; return u(band(rshift(t(n), field), 2 ^ width - 1)) end +end + +-- buffer: fixed-size byte buffers (subset used by the port), on FFI memory. Little-endian like Luau. +if not buffer then + ffi.cdef("void *memmove(void *dst, const void *src, size_t n); void *memset(void *s, int c, size_t n);") + local C = ffi.C + local u8p, u32p, f64p = ffi.typeof("uint8_t*"), ffi.typeof("uint32_t*"), ffi.typeof("double*") + buffer = {} + function buffer.create(n) + local mem = ffi.new("uint8_t[?]", math.max(n, 1)) -- zero-filled + return { p = ffi.cast(u8p, mem), n = n, mem = mem } + end + function buffer.len(b) return b.n end + function buffer.readu8(b, o) return b.p[o] end + function buffer.writeu8(b, o, v) b.p[o] = v end + function buffer.readu32(b, o) return ffi.cast(u32p, b.p + o)[0] end + function buffer.writeu32(b, o, v) ffi.cast(u32p, b.p + o)[0] = v % TWO32 end + function buffer.readf64(b, o) return ffi.cast(f64p, b.p + o)[0] end + function buffer.writef64(b, o, v) ffi.cast(f64p, b.p + o)[0] = v end + function buffer.readstring(b, o, n) return ffi.string(b.p + o, n) end + function buffer.writestring(b, o, s, n) ffi.copy(b.p + o, s, n or #s) end + function buffer.copy(dst, doff, src, soff, n) + soff = soff or 0 + n = n or (src.n - soff) + if n > 0 then C.memmove(dst.p + doff, src.p + soff, n) end + end + function buffer.fill(b, o, v, n) n = n or (b.n - o); if n > 0 then C.memset(b.p + o, v, n) end end + function buffer.tostring(b) return ffi.string(b.p, b.n) end + function buffer.fromstring(s) local b = buffer.create(#s); ffi.copy(b.p, s, #s); return b end +end + +-- table / math / utf8 additions from Luau / Lua 5.3 +do + local ok, clear = pcall(require, "table.clear") + table.clear = table.clear or (ok and clear) or function(t) for k in pairs(t) do t[k] = nil end end + table.unpack = table.unpack or unpack + table.find = table.find or function(t, v, init) for i = init or 1, #t do if t[i] == v then return i end end end + table.move = table.move or function(a1, f, e, t, a2) + a2 = a2 or a1 + if e >= f then + if t > f or t > e or a1 ~= a2 then for i = 0, e - f do a2[t + i] = a1[f + i] end + else for i = e - f, 0, -1 do a2[t + i] = a1[f + i] end end + end + return a2 + end + math.pow = math.pow or function(a, b) return a ^ b end +end + +if not utf8 then + utf8 = { charpattern = "[\0-\x7F\xC2-\xFD][\x80-\xBF]*" } + function utf8.char(...) + local out = {} + for i = 1, select("#", ...) do + local c = select(i, ...) + if c < 0x80 then out[#out + 1] = string.char(c) + elseif c < 0x800 then out[#out + 1] = string.char(0xC0 + floor(c / 64), 0x80 + c % 64) + elseif c < 0x10000 then out[#out + 1] = string.char(0xE0 + floor(c / 4096), 0x80 + floor(c / 64) % 64, 0x80 + c % 64) + else out[#out + 1] = string.char(0xF0 + floor(c / 262144), 0x80 + floor(c / 4096) % 64, 0x80 + floor(c / 64) % 64, 0x80 + c % 64) end + end + return table.concat(out) + end + local function decode(s, i) + local c = s:byte(i) + if not c then return nil end + if c < 0x80 then return c, 1 end + if c < 0xE0 then return (c % 32) * 64 + s:byte(i + 1) % 64, 2 end + if c < 0xF0 then return (c % 16) * 4096 + (s:byte(i + 1) % 64) * 64 + s:byte(i + 2) % 64, 3 end + return (c % 8) * 262144 + (s:byte(i + 1) % 64) * 4096 + (s:byte(i + 2) % 64) * 64 + s:byte(i + 3) % 64, 4 + end + function utf8.codes(s) + local i = 1 + return function() + if i > #s then return nil end + local c, n = decode(s, i) + local p = i; i = i + n + return p, c + end + end + function utf8.codepoint(s, i, j) + i = i or 1; j = j or i + local out = {} + while i <= j do local c, n = decode(s, i); out[#out + 1] = c; i = i + n end + return unpack(out) + end + function utf8.len(s) local n = 0; for _ in utf8.codes(s) do n = n + 1 end return n end + function utf8.offset(s, n, i) + i = i or (n >= 0 and 1 or #s + 1) + if n > 0 then + n = n - 1 + while n > 0 and i <= #s do i = i + 1; while i <= #s and s:byte(i) >= 0x80 and s:byte(i) < 0xC0 do i = i + 1 end; n = n - 1 end + if n > 0 then return nil end + return i + end + return i + end +end +--@compat-end + local floor, min = math.floor, math.min local ImGui_ImplLOVE_GetBackendData diff --git a/build/imgui_love.linemap.json b/build/imgui_love.linemap.json index 730cd25..01b6734 100644 --- a/build/imgui_love.linemap.json +++ b/build/imgui_love.linemap.json @@ -1 +1 @@ -[[149, "imgui.lua"], [18609, "imgui_demo.lua"], [25894, "imgui_draw.lua"], [31132, "imgui_h.lua"], [33330, "imgui_impl_love.lua"], [33730, "imgui_internal.lua"], [36902, "imgui_tables.lua"], [41249, "imgui_widgets.lua"], [51004, "imstb_rectpack.lua"], [51450, "imstb_textedit.lua"], [52691, "imstb_truetype.lua"], [55159, "imstd_minstdio.lua"]] \ No newline at end of file +[[149, "imgui.lua"], [18609, "imgui_demo.lua"], [25894, "imgui_draw.lua"], [31132, "imgui_h.lua"], [33330, "imgui_impl_love.lua"], [33870, "imgui_internal.lua"], [37042, "imgui_tables.lua"], [41389, "imgui_widgets.lua"], [51144, "imstb_rectpack.lua"], [51590, "imstb_textedit.lua"], [52831, "imstb_truetype.lua"], [55299, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/imgui_love.lua b/build/imgui_love.lua index 302afa6..c2c0ba7 100644 --- a/build/imgui_love.lua +++ b/build/imgui_love.lua @@ -1,10 +1,10 @@ -- GENERATED by tools/bundle.py --target love - do not edit +local ffi = require("ffi") +local floor = math.floor -- Luau standard library pieces the port uses, for LuaJIT (LÖVE). Prepended by `tools/bundle.py --target love` only: -- the Roblox build uses Luau's native bit32/buffer/table/utf8 and never sees this file. -local ffi = require("ffi") local bit = require("bit") -local floor = math.floor local TWO32 = 4294967296 -- bit32: unsigned 32-bit results like Luau/Lua 5.2 (LuaJIT's `bit` returns signed values) @@ -33335,7 +33335,147 @@ __FILES['imgui_impl_love.lua'] = function() --- love.draw: ImGui_ImplLove.RenderDrawData(ImGui.GetDrawData()) --- forward love.mousemoved/mousepressed/mousereleased/wheelmoved/keypressed/keyreleased/textinput to ImGui_ImplLove.* +-- [SECTION] COMPAT (Luau stdlib for LuaJIT). tools/bundle.py --target love runs this section before the library. +--@compat-begin local ffi = require("ffi") +local floor = math.floor +-- Luau standard library pieces the port uses, for LuaJIT (LÖVE). Prepended by `tools/bundle.py --target love` only: +-- the Roblox build uses Luau's native bit32/buffer/table/utf8 and never sees this file. + +local bit = require("bit") +local TWO32 = 4294967296 + +-- bit32: unsigned 32-bit results like Luau/Lua 5.2 (LuaJIT's `bit` returns signed values) +if not bit32 then + local band, bor, bxor, bnot, lshift, rshift, arshift, tobit = bit.band, bit.bor, bit.bxor, bit.bnot, bit.lshift, bit.rshift, bit.arshift, bit.tobit + local function u(x) return x % TWO32 end + local function t(x) return tobit(x % TWO32) end -- any number -> int32 (bit.* wraps doubles >= 2^31 unreliably) + bit32 = {} + function bit32.band(a, b, ...) + if a == nil then return TWO32 - 1 end + if b == nil then return u(t(a)) end + local r = band(t(a), t(b)) + if ... ~= nil then for i = 1, select("#", ...) do r = band(r, t((select(i, ...)))) end end + return u(r) + end + function bit32.bor(a, b, ...) + if a == nil then return 0 end + if b == nil then return u(t(a)) end + local r = bor(t(a), t(b)) + if ... ~= nil then for i = 1, select("#", ...) do r = bor(r, t((select(i, ...)))) end end + return u(r) + end + function bit32.bxor(a, b, ...) + if a == nil then return 0 end + if b == nil then return u(t(a)) end + local r = bxor(t(a), t(b)) + if ... ~= nil then for i = 1, select("#", ...) do r = bxor(r, t((select(i, ...)))) end end + return u(r) + end + function bit32.bnot(a) return u(bnot(t(a))) end + function bit32.lshift(a, n) if n >= 32 or n <= -32 then return 0 end if n < 0 then return u(rshift(t(a), -n)) end return u(lshift(t(a), n)) end + function bit32.rshift(a, n) if n >= 32 or n <= -32 then return 0 end if n < 0 then return u(lshift(t(a), -n)) end return u(rshift(t(a), n)) end + function bit32.arshift(a, n) if n >= 32 then n = 31 end return u(arshift(t(a), n)) end + function bit32.btest(...) return bit32.band(...) ~= 0 end + function bit32.extract(n, field, width) width = width or 1; return u(band(rshift(t(n), field), 2 ^ width - 1)) end +end + +-- buffer: fixed-size byte buffers (subset used by the port), on FFI memory. Little-endian like Luau. +if not buffer then + ffi.cdef("void *memmove(void *dst, const void *src, size_t n); void *memset(void *s, int c, size_t n);") + local C = ffi.C + local u8p, u32p, f64p = ffi.typeof("uint8_t*"), ffi.typeof("uint32_t*"), ffi.typeof("double*") + buffer = {} + function buffer.create(n) + local mem = ffi.new("uint8_t[?]", math.max(n, 1)) -- zero-filled + return { p = ffi.cast(u8p, mem), n = n, mem = mem } + end + function buffer.len(b) return b.n end + function buffer.readu8(b, o) return b.p[o] end + function buffer.writeu8(b, o, v) b.p[o] = v end + function buffer.readu32(b, o) return ffi.cast(u32p, b.p + o)[0] end + function buffer.writeu32(b, o, v) ffi.cast(u32p, b.p + o)[0] = v % TWO32 end + function buffer.readf64(b, o) return ffi.cast(f64p, b.p + o)[0] end + function buffer.writef64(b, o, v) ffi.cast(f64p, b.p + o)[0] = v end + function buffer.readstring(b, o, n) return ffi.string(b.p + o, n) end + function buffer.writestring(b, o, s, n) ffi.copy(b.p + o, s, n or #s) end + function buffer.copy(dst, doff, src, soff, n) + soff = soff or 0 + n = n or (src.n - soff) + if n > 0 then C.memmove(dst.p + doff, src.p + soff, n) end + end + function buffer.fill(b, o, v, n) n = n or (b.n - o); if n > 0 then C.memset(b.p + o, v, n) end end + function buffer.tostring(b) return ffi.string(b.p, b.n) end + function buffer.fromstring(s) local b = buffer.create(#s); ffi.copy(b.p, s, #s); return b end +end + +-- table / math / utf8 additions from Luau / Lua 5.3 +do + local ok, clear = pcall(require, "table.clear") + table.clear = table.clear or (ok and clear) or function(t) for k in pairs(t) do t[k] = nil end end + table.unpack = table.unpack or unpack + table.find = table.find or function(t, v, init) for i = init or 1, #t do if t[i] == v then return i end end end + table.move = table.move or function(a1, f, e, t, a2) + a2 = a2 or a1 + if e >= f then + if t > f or t > e or a1 ~= a2 then for i = 0, e - f do a2[t + i] = a1[f + i] end + else for i = e - f, 0, -1 do a2[t + i] = a1[f + i] end end + end + return a2 + end + math.pow = math.pow or function(a, b) return a ^ b end +end + +if not utf8 then + utf8 = { charpattern = "[\0-\x7F\xC2-\xFD][\x80-\xBF]*" } + function utf8.char(...) + local out = {} + for i = 1, select("#", ...) do + local c = select(i, ...) + if c < 0x80 then out[#out + 1] = string.char(c) + elseif c < 0x800 then out[#out + 1] = string.char(0xC0 + floor(c / 64), 0x80 + c % 64) + elseif c < 0x10000 then out[#out + 1] = string.char(0xE0 + floor(c / 4096), 0x80 + floor(c / 64) % 64, 0x80 + c % 64) + else out[#out + 1] = string.char(0xF0 + floor(c / 262144), 0x80 + floor(c / 4096) % 64, 0x80 + floor(c / 64) % 64, 0x80 + c % 64) end + end + return table.concat(out) + end + local function decode(s, i) + local c = s:byte(i) + if not c then return nil end + if c < 0x80 then return c, 1 end + if c < 0xE0 then return (c % 32) * 64 + s:byte(i + 1) % 64, 2 end + if c < 0xF0 then return (c % 16) * 4096 + (s:byte(i + 1) % 64) * 64 + s:byte(i + 2) % 64, 3 end + return (c % 8) * 262144 + (s:byte(i + 1) % 64) * 4096 + (s:byte(i + 2) % 64) * 64 + s:byte(i + 3) % 64, 4 + end + function utf8.codes(s) + local i = 1 + return function() + if i > #s then return nil end + local c, n = decode(s, i) + local p = i; i = i + n + return p, c + end + end + function utf8.codepoint(s, i, j) + i = i or 1; j = j or i + local out = {} + while i <= j do local c, n = decode(s, i); out[#out + 1] = c; i = i + n end + return unpack(out) + end + function utf8.len(s) local n = 0; for _ in utf8.codes(s) do n = n + 1 end return n end + function utf8.offset(s, n, i) + i = i or (n >= 0 and 1 or #s + 1) + if n > 0 then + n = n - 1 + while n > 0 and i <= #s do i = i + 1; while i <= #s and s:byte(i) >= 0x80 and s:byte(i) < 0xC0 do i = i + 1 end; n = n - 1 end + if n > 0 then return nil end + return i + end + return i + end +end +--@compat-end + local floor, min = math.floor, math.min local ImGui_ImplLOVE_GetBackendData diff --git a/compat/luajit.lua b/compat/luajit.lua deleted file mode 100644 index 5d163b1..0000000 --- a/compat/luajit.lua +++ /dev/null @@ -1,137 +0,0 @@ --- Luau standard library pieces the port uses, for LuaJIT (LÖVE). Prepended by `tools/bundle.py --target love` only: --- the Roblox build uses Luau's native bit32/buffer/table/utf8 and never sees this file. - -local ffi = require("ffi") -local bit = require("bit") -local floor = math.floor -local TWO32 = 4294967296 - --- bit32: unsigned 32-bit results like Luau/Lua 5.2 (LuaJIT's `bit` returns signed values) -if not bit32 then - local band, bor, bxor, bnot, lshift, rshift, arshift, tobit = bit.band, bit.bor, bit.bxor, bit.bnot, bit.lshift, bit.rshift, bit.arshift, bit.tobit - local function u(x) return x % TWO32 end - local function t(x) return tobit(x % TWO32) end -- any number -> int32 (bit.* wraps doubles >= 2^31 unreliably) - bit32 = {} - function bit32.band(a, b, ...) - if a == nil then return TWO32 - 1 end - if b == nil then return u(t(a)) end - local r = band(t(a), t(b)) - if ... ~= nil then for i = 1, select("#", ...) do r = band(r, t((select(i, ...)))) end end - return u(r) - end - function bit32.bor(a, b, ...) - if a == nil then return 0 end - if b == nil then return u(t(a)) end - local r = bor(t(a), t(b)) - if ... ~= nil then for i = 1, select("#", ...) do r = bor(r, t((select(i, ...)))) end end - return u(r) - end - function bit32.bxor(a, b, ...) - if a == nil then return 0 end - if b == nil then return u(t(a)) end - local r = bxor(t(a), t(b)) - if ... ~= nil then for i = 1, select("#", ...) do r = bxor(r, t((select(i, ...)))) end end - return u(r) - end - function bit32.bnot(a) return u(bnot(t(a))) end - function bit32.lshift(a, n) if n >= 32 or n <= -32 then return 0 end if n < 0 then return u(rshift(t(a), -n)) end return u(lshift(t(a), n)) end - function bit32.rshift(a, n) if n >= 32 or n <= -32 then return 0 end if n < 0 then return u(lshift(t(a), -n)) end return u(rshift(t(a), n)) end - function bit32.arshift(a, n) if n >= 32 then n = 31 end return u(arshift(t(a), n)) end - function bit32.btest(...) return bit32.band(...) ~= 0 end - function bit32.extract(n, field, width) width = width or 1; return u(band(rshift(t(n), field), 2 ^ width - 1)) end -end - --- buffer: fixed-size byte buffers (subset used by the port), on FFI memory. Little-endian like Luau. -if not buffer then - ffi.cdef("void *memmove(void *dst, const void *src, size_t n); void *memset(void *s, int c, size_t n);") - local C = ffi.C - local u8p, u32p, f64p = ffi.typeof("uint8_t*"), ffi.typeof("uint32_t*"), ffi.typeof("double*") - buffer = {} - function buffer.create(n) - local mem = ffi.new("uint8_t[?]", math.max(n, 1)) -- zero-filled - return { p = ffi.cast(u8p, mem), n = n, mem = mem } - end - function buffer.len(b) return b.n end - function buffer.readu8(b, o) return b.p[o] end - function buffer.writeu8(b, o, v) b.p[o] = v end - function buffer.readu32(b, o) return ffi.cast(u32p, b.p + o)[0] end - function buffer.writeu32(b, o, v) ffi.cast(u32p, b.p + o)[0] = v % TWO32 end - function buffer.readf64(b, o) return ffi.cast(f64p, b.p + o)[0] end - function buffer.writef64(b, o, v) ffi.cast(f64p, b.p + o)[0] = v end - function buffer.readstring(b, o, n) return ffi.string(b.p + o, n) end - function buffer.writestring(b, o, s, n) ffi.copy(b.p + o, s, n or #s) end - function buffer.copy(dst, doff, src, soff, n) - soff = soff or 0 - n = n or (src.n - soff) - if n > 0 then C.memmove(dst.p + doff, src.p + soff, n) end - end - function buffer.fill(b, o, v, n) n = n or (b.n - o); if n > 0 then C.memset(b.p + o, v, n) end end - function buffer.tostring(b) return ffi.string(b.p, b.n) end - function buffer.fromstring(s) local b = buffer.create(#s); ffi.copy(b.p, s, #s); return b end -end - --- table / math / utf8 additions from Luau / Lua 5.3 -do - local ok, clear = pcall(require, "table.clear") - table.clear = table.clear or (ok and clear) or function(t) for k in pairs(t) do t[k] = nil end end - table.unpack = table.unpack or unpack - table.find = table.find or function(t, v, init) for i = init or 1, #t do if t[i] == v then return i end end end - table.move = table.move or function(a1, f, e, t, a2) - a2 = a2 or a1 - if e >= f then - if t > f or t > e or a1 ~= a2 then for i = 0, e - f do a2[t + i] = a1[f + i] end - else for i = e - f, 0, -1 do a2[t + i] = a1[f + i] end end - end - return a2 - end - math.pow = math.pow or function(a, b) return a ^ b end -end - -if not utf8 then - utf8 = { charpattern = "[\0-\x7F\xC2-\xFD][\x80-\xBF]*" } - function utf8.char(...) - local out = {} - for i = 1, select("#", ...) do - local c = select(i, ...) - if c < 0x80 then out[#out + 1] = string.char(c) - elseif c < 0x800 then out[#out + 1] = string.char(0xC0 + floor(c / 64), 0x80 + c % 64) - elseif c < 0x10000 then out[#out + 1] = string.char(0xE0 + floor(c / 4096), 0x80 + floor(c / 64) % 64, 0x80 + c % 64) - else out[#out + 1] = string.char(0xF0 + floor(c / 262144), 0x80 + floor(c / 4096) % 64, 0x80 + floor(c / 64) % 64, 0x80 + c % 64) end - end - return table.concat(out) - end - local function decode(s, i) - local c = s:byte(i) - if not c then return nil end - if c < 0x80 then return c, 1 end - if c < 0xE0 then return (c % 32) * 64 + s:byte(i + 1) % 64, 2 end - if c < 0xF0 then return (c % 16) * 4096 + (s:byte(i + 1) % 64) * 64 + s:byte(i + 2) % 64, 3 end - return (c % 8) * 262144 + (s:byte(i + 1) % 64) * 4096 + (s:byte(i + 2) % 64) * 64 + s:byte(i + 3) % 64, 4 - end - function utf8.codes(s) - local i = 1 - return function() - if i > #s then return nil end - local c, n = decode(s, i) - local p = i; i = i + n - return p, c - end - end - function utf8.codepoint(s, i, j) - i = i or 1; j = j or i - local out = {} - while i <= j do local c, n = decode(s, i); out[#out + 1] = c; i = i + n end - return unpack(out) - end - function utf8.len(s) local n = 0; for _ in utf8.codes(s) do n = n + 1 end return n end - function utf8.offset(s, n, i) - i = i or (n >= 0 and 1 or #s + 1) - if n > 0 then - n = n - 1 - while n > 0 and i <= #s do i = i + 1; while i <= #s and s:byte(i) >= 0x80 and s:byte(i) < 0xC0 do i = i + 1 end; n = n - 1 end - if n > 0 then return nil end - return i - end - return i - end -end diff --git a/docs/PORTING.md b/docs/PORTING.md index 97d50ec..5251214 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -21,7 +21,7 @@ there, but keep this port's existing data structures and conventions. Read the s ## Portability (LÖVE build) - Library code must stay valid **Lua 5.1/LuaJIT syntax** apart from `@native` lines (stripped for LÖVE): no `+=`, `//`, `continue`, type annotations, `if`-expressions or string interpolation. Luau library calls are fine only if - `compat/luajit.lua` shims them (`bit32`, `buffer`, `table.clear/find/move/unpack`, `utf8`). + the COMPAT section of `backends/imgui_impl_love.lua` shims 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) diff --git a/tools/bundle.py b/tools/bundle.py index f196796..fea9769 100644 --- a/tools/bundle.py +++ b/tools/bundle.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # 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) -# --target love: build/imgui_love.lua for LÖVE/LuaJIT instead (LÖVE backend, compat/luajit.lua shims, @native stripped). +# --target love: build/imgui_love.lua for LÖVE/LuaJIT instead (LÖVE backend, shims from the LÖVE backend's COMPAT section, @native stripped). # The default (roblox) build is unaffected by it. # 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. @@ -34,7 +34,7 @@ for src in FILES.values(): out = ["--!optimize 2", "-- GENERATED by tools/bundle.py from *.lua, backends/, fonts/ - do not edit", ""] if LOVE: - out = ["-- GENERATED by tools/bundle.py --target love - do not edit", open(os.path.join(ROOT, "compat", "luajit.lua")).read(), ""] + out = ["-- GENERATED by tools/bundle.py --target love - do not edit", re.search(r"--@compat-begin\n(.*?)--@compat-end", open(os.path.join(ROOT, "backends", "imgui_impl_love.lua")).read(), re.S).group(1), ""] out += [ "local __FILES, __LOADED = {}, {}", "function IM_INCLUDE(name)",