SetNextWindowCached(key): opt-in cached windows on top of the refresh policy (skip contents while idle), t_cached_window test, README snippet
Build & Release / build (push) Canceled after 0s
Build & Release / build (push) Canceled after 0s
This commit is contained in:
@@ -244,6 +244,20 @@ Measured headless (Luau CLI, Studio-like native code generation), demo window op
|
||||
|
||||
Most of the remaining cost is widget logic (your UI code and Dear ImGui's per-widget work), which runs every frame by design. Use `ImGuiListClipper` for long lists.
|
||||
|
||||
Windows that rarely change can opt in to caching: `ImGui.SetNextWindowCached(key)` keeps last frame's drawing and makes `Begin` return `visible = false`, so your code for that window doesn't run. It refreshes when `key` changes, while the window is hovered, focused or active (plus one frame after), and when it appears, moves, resizes, scrolls, collapses or is docked. An idle 200-widget window goes from ~2.2 ms to ~0.15 ms per frame.
|
||||
|
||||
```lua
|
||||
local stats = { version = 0, kills = 0 }
|
||||
local function StatsWindow()
|
||||
ImGui.SetNextWindowCached(stats.version) -- bump stats.version whenever stats change
|
||||
local _, visible = ImGui.Begin("Stats")
|
||||
if visible then -- false while the cached contents are reused
|
||||
ImGui.Text("Kills: %d", stats.kills)
|
||||
end
|
||||
ImGui.End()
|
||||
end
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
`tools/test/run.py` runs any LocalScript headless against a mocked Roblox (`tools/test/roblox_mock.luau`):
|
||||
|
||||
@@ -47,6 +47,8 @@ jobs:
|
||||
luajit -bl dist/love-nodemo/imgui_love.lua > /dev/null
|
||||
python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/r.txt
|
||||
grep -q "README OK" /tmp/r.txt
|
||||
python3 tools/test/run.py --main tools/test/scripts/t_cached_window.luau --frames 130 --quiet --roblox --timeout 300 | tee /tmp/c.txt
|
||||
grep -q "CACHED OK" /tmp/c.txt
|
||||
python3 tools/bundle.py --target love >/dev/null
|
||||
luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic
|
||||
luajit tools/test/love/t_examples.lua
|
||||
|
||||
@@ -241,6 +241,20 @@ Measured headless (Luau CLI, Studio-like native code generation), demo window op
|
||||
|
||||
Most of the remaining cost is widget logic (your UI code and Dear ImGui's per-widget work), which runs every frame by design. Use `ImGuiListClipper` for long lists.
|
||||
|
||||
Windows that rarely change can opt in to caching: `ImGui.SetNextWindowCached(key)` keeps last frame's drawing and makes `Begin` return `visible = false`, so your code for that window doesn't run. It refreshes when `key` changes, while the window is hovered, focused or active (plus one frame after), and when it appears, moves, resizes, scrolls, collapses or is docked. An idle 200-widget window goes from ~2.2 ms to ~0.15 ms per frame.
|
||||
|
||||
```lua
|
||||
local stats = { version = 0, kills = 0 }
|
||||
local function StatsWindow()
|
||||
ImGui.SetNextWindowCached(stats.version) -- bump stats.version whenever stats change
|
||||
local _, visible = ImGui.Begin("Stats")
|
||||
if visible then -- false while the cached contents are reused
|
||||
ImGui.Text("Kills: %d", stats.kills)
|
||||
end
|
||||
ImGui.End()
|
||||
end
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
`tools/test/run.py` runs any LocalScript headless against a mocked Roblox (`tools/test/roblox_mock.luau`):
|
||||
|
||||
@@ -1 +1 @@
|
||||
[[12, "imgui.lua"], [18472, "imgui_demo.lua"], [25757, "imgui_draw.lua"], [30995, "imgui_h.lua"], [33193, "imgui_impl_roblox.lua"], [34525, "imgui_impl_roblox_fonts.lua"], [40928, "imgui_internal.lua"], [44100, "imgui_tables.lua"], [48447, "imgui_widgets.lua"], [58202, "imstb_rectpack.lua"], [58648, "imstb_textedit.lua"], [59889, "imstb_truetype.lua"], [62357, "imstd_minstdio.lua"]]
|
||||
[[12, "imgui.lua"], [18510, "imgui_demo.lua"], [25795, "imgui_draw.lua"], [31033, "imgui_h.lua"], [33231, "imgui_impl_roblox.lua"], [34563, "imgui_impl_roblox_fonts.lua"], [40966, "imgui_internal.lua"], [44142, "imgui_tables.lua"], [48489, "imgui_widgets.lua"], [58244, "imstb_rectpack.lua"], [58690, "imstb_textedit.lua"], [59931, "imstb_truetype.lua"], [62399, "imstd_minstdio.lua"]]
|
||||
+43
-1
@@ -8857,6 +8857,26 @@ function ImGui.UpdateWindowSkipRefresh(window)
|
||||
end
|
||||
|
||||
if bit32.band(g.NextWindowData.RefreshFlagsVal, ImGuiWindowRefreshFlags.TryToAvoidRefresh) ~= 0 then
|
||||
-- [Port] SetNextWindowCached(): refresh on any change since the last refresh (and one frame after being busy)
|
||||
if g.NextWindowData.CacheKeySet then
|
||||
local function related(w) return w ~= nil and (w.RootWindow == window.RootWindow or ImGui.IsWindowWithinBeginStackOf(w.RootWindow, window)) end
|
||||
local busy = window.Appearing or window.Hidden or window.DockIsActive or related(g.HoveredWindow) or related(g.NavWindow) or related(g.ActiveIdWindow) or (g.MovingWindow ~= nil and related(g.MovingWindow))
|
||||
local c, key, ds, tex = window.CacheSnap, g.NextWindowData.CacheKeyVal, g.IO.DisplaySize, g.IO.Fonts.TexData
|
||||
if busy or c == nil or c.Busy or c.Key ~= key or c.Tex ~= tex or c.Collapsed ~= window.Collapsed
|
||||
or c.PosX ~= window.Pos.x or c.PosY ~= window.Pos.y or c.SizeX ~= window.SizeFull.x or c.SizeY ~= window.SizeFull.y
|
||||
or c.ScrollX ~= window.Scroll.x or c.ScrollY ~= window.Scroll.y or window.ScrollTarget.x < FLT_MAX or window.ScrollTarget.y < FLT_MAX
|
||||
or c.DisplayX ~= ds.x or c.DisplayY ~= ds.y then
|
||||
c = c or {}
|
||||
window.CacheSnap = c
|
||||
c.Busy, c.Key, c.Tex, c.Collapsed = busy, key, tex, window.Collapsed
|
||||
c.PosX, c.PosY, c.SizeX, c.SizeY = window.Pos.x, window.Pos.y, window.SizeFull.x, window.SizeFull.y
|
||||
c.ScrollX, c.ScrollY, c.DisplayX, c.DisplayY = window.Scroll.x, window.Scroll.y, ds.x, ds.y
|
||||
return
|
||||
end
|
||||
-- skip: widgets early-out on SkipItems and Begin() returns visible=false; End() restores it
|
||||
window.CacheSkipItemsBackup = window.SkipItems
|
||||
window.SkipItems = true
|
||||
end
|
||||
-- FIXME-IDLE: Tests for e.g. mouse clicks or keyboard while focused.
|
||||
if window.Appearing then -- If currently appearing
|
||||
return
|
||||
@@ -9488,6 +9508,20 @@ function ImGui.SetNextWindowRefreshPolicy(flags)
|
||||
local g = GImGui
|
||||
g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasRefreshPolicy)
|
||||
g.NextWindowData.RefreshFlagsVal = flags
|
||||
g.NextWindowData.CacheKeySet = false
|
||||
end
|
||||
|
||||
-- [Port] Opt-in cached window (built on the refresh policy above). While nothing that could change the window
|
||||
-- happened, Begin() keeps last frame's draw list and returns visible=false, so `if visible then ... end` skips your code.
|
||||
-- It refreshes when `key` changes (pass e.g. a version number of the data shown), while the window (or a popup/child
|
||||
-- of it) is hovered, focused or active and for one frame after, and when it appears, moves, resizes, scrolls,
|
||||
-- collapses, is docked, or the display size or font atlas texture changes.
|
||||
-- Anything else that changes the contents (style, fonts, time) must be part of `key`.
|
||||
function ImGui.SetNextWindowCached(key)
|
||||
local g = GImGui
|
||||
ImGui.SetNextWindowRefreshPolicy(bit32.bor(ImGuiWindowRefreshFlags.TryToAvoidRefresh, ImGuiWindowRefreshFlags.RefreshOnHover, ImGuiWindowRefreshFlags.RefreshOnFocus))
|
||||
g.NextWindowData.CacheKeySet = true
|
||||
g.NextWindowData.CacheKeyVal = key
|
||||
end
|
||||
|
||||
function ImGui.GetWindowDpiScale()
|
||||
@@ -10690,6 +10724,10 @@ function ImGui.End()
|
||||
IM_ASSERT(window.DrawList == nil)
|
||||
window.DrawList = window.DrawListInst
|
||||
end
|
||||
if window.CacheSkipItemsBackup ~= nil then -- [Port] SetNextWindowCached()
|
||||
window.SkipItems = window.CacheSkipItemsBackup
|
||||
window.CacheSkipItemsBackup = nil
|
||||
end
|
||||
|
||||
-- Stop logging
|
||||
if g.LogWindow == window then -- FIXME: add more options for scope of logging
|
||||
@@ -42163,7 +42201,9 @@ function ImGuiNextWindowData()
|
||||
SizeCallbackUserData = nil,
|
||||
BgAlphaVal = nil,
|
||||
MenuBarOffsetMinVal = ImVec2(),
|
||||
RefreshFlagsVal = nil
|
||||
RefreshFlagsVal = nil,
|
||||
CacheKeySet = false, -- [Port] SetNextWindowCached()
|
||||
CacheKeyVal = nil
|
||||
}, IMGUI_NEXT_WINDOW_DATA)
|
||||
end
|
||||
|
||||
@@ -43335,6 +43375,8 @@ function ImGuiWindow(ctx, name)
|
||||
SkipItems = false,
|
||||
|
||||
SkipRefresh = false,
|
||||
CacheSnap = nil, -- [Port] SetNextWindowCached(): state at the last refresh
|
||||
CacheSkipItemsBackup = nil,
|
||||
|
||||
Appearing = false,
|
||||
|
||||
|
||||
@@ -516,7 +516,7 @@ export type ImGuiAPI = {
|
||||
NavUpdateWindowingTarget: (...any) -> ...any,
|
||||
NewFrame: () -> (),
|
||||
NewLine: () -> (),
|
||||
NewTextBuffer: (...any) -> ...any,
|
||||
NewTextBuffer: (str: string, size: number?) -> {number},
|
||||
NextColumn: () -> (),
|
||||
OpenPopup: (...any) -> ...any,
|
||||
OpenPopupEx: (...any) -> ...any,
|
||||
@@ -643,6 +643,7 @@ export type ImGuiAPI = {
|
||||
SetNextItemStorageID: (storage_id: number) -> (),
|
||||
SetNextItemWidth: (item_width: number) -> (),
|
||||
SetNextWindowBgAlpha: (alpha: number) -> (),
|
||||
SetNextWindowCached: (key: any) -> (),
|
||||
SetNextWindowChildFlags: (...any) -> ...any,
|
||||
SetNextWindowClass: (window_class: any) -> (),
|
||||
SetNextWindowCollapsed: (collapsed: boolean, cond: number?) -> (),
|
||||
@@ -823,7 +824,7 @@ export type ImGuiAPI = {
|
||||
TestKeyOwner: (...any) -> ...any,
|
||||
Text: (fmt: string, ...any) -> (),
|
||||
TextAligned: (...any) -> ...any,
|
||||
TextBufferToString: (...any) -> ...any,
|
||||
TextBufferToString: (buf: {number}) -> string,
|
||||
TextColored: (col: ImVec4, fmt: string, ...any) -> (),
|
||||
TextDisabled: (fmt: string, ...any) -> (),
|
||||
TextEx: (...any) -> ...any,
|
||||
|
||||
@@ -1 +1 @@
|
||||
[[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"]]
|
||||
[[149, "imgui.lua"], [18647, "imgui_demo.lua"], [25932, "imgui_draw.lua"], [31170, "imgui_h.lua"], [33368, "imgui_impl_love.lua"], [33908, "imgui_internal.lua"], [37084, "imgui_tables.lua"], [41431, "imgui_widgets.lua"], [51186, "imstb_rectpack.lua"], [51632, "imstb_textedit.lua"], [52873, "imstb_truetype.lua"], [55341, "imstd_minstdio.lua"]]
|
||||
+43
-1
@@ -8994,6 +8994,26 @@ function ImGui.UpdateWindowSkipRefresh(window)
|
||||
end
|
||||
|
||||
if bit32.band(g.NextWindowData.RefreshFlagsVal, ImGuiWindowRefreshFlags.TryToAvoidRefresh) ~= 0 then
|
||||
-- [Port] SetNextWindowCached(): refresh on any change since the last refresh (and one frame after being busy)
|
||||
if g.NextWindowData.CacheKeySet then
|
||||
local function related(w) return w ~= nil and (w.RootWindow == window.RootWindow or ImGui.IsWindowWithinBeginStackOf(w.RootWindow, window)) end
|
||||
local busy = window.Appearing or window.Hidden or window.DockIsActive or related(g.HoveredWindow) or related(g.NavWindow) or related(g.ActiveIdWindow) or (g.MovingWindow ~= nil and related(g.MovingWindow))
|
||||
local c, key, ds, tex = window.CacheSnap, g.NextWindowData.CacheKeyVal, g.IO.DisplaySize, g.IO.Fonts.TexData
|
||||
if busy or c == nil or c.Busy or c.Key ~= key or c.Tex ~= tex or c.Collapsed ~= window.Collapsed
|
||||
or c.PosX ~= window.Pos.x or c.PosY ~= window.Pos.y or c.SizeX ~= window.SizeFull.x or c.SizeY ~= window.SizeFull.y
|
||||
or c.ScrollX ~= window.Scroll.x or c.ScrollY ~= window.Scroll.y or window.ScrollTarget.x < FLT_MAX or window.ScrollTarget.y < FLT_MAX
|
||||
or c.DisplayX ~= ds.x or c.DisplayY ~= ds.y then
|
||||
c = c or {}
|
||||
window.CacheSnap = c
|
||||
c.Busy, c.Key, c.Tex, c.Collapsed = busy, key, tex, window.Collapsed
|
||||
c.PosX, c.PosY, c.SizeX, c.SizeY = window.Pos.x, window.Pos.y, window.SizeFull.x, window.SizeFull.y
|
||||
c.ScrollX, c.ScrollY, c.DisplayX, c.DisplayY = window.Scroll.x, window.Scroll.y, ds.x, ds.y
|
||||
return
|
||||
end
|
||||
-- skip: widgets early-out on SkipItems and Begin() returns visible=false; End() restores it
|
||||
window.CacheSkipItemsBackup = window.SkipItems
|
||||
window.SkipItems = true
|
||||
end
|
||||
-- FIXME-IDLE: Tests for e.g. mouse clicks or keyboard while focused.
|
||||
if window.Appearing then -- If currently appearing
|
||||
return
|
||||
@@ -9625,6 +9645,20 @@ function ImGui.SetNextWindowRefreshPolicy(flags)
|
||||
local g = GImGui
|
||||
g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasRefreshPolicy)
|
||||
g.NextWindowData.RefreshFlagsVal = flags
|
||||
g.NextWindowData.CacheKeySet = false
|
||||
end
|
||||
|
||||
-- [Port] Opt-in cached window (built on the refresh policy above). While nothing that could change the window
|
||||
-- happened, Begin() keeps last frame's draw list and returns visible=false, so `if visible then ... end` skips your code.
|
||||
-- It refreshes when `key` changes (pass e.g. a version number of the data shown), while the window (or a popup/child
|
||||
-- of it) is hovered, focused or active and for one frame after, and when it appears, moves, resizes, scrolls,
|
||||
-- collapses, is docked, or the display size or font atlas texture changes.
|
||||
-- Anything else that changes the contents (style, fonts, time) must be part of `key`.
|
||||
function ImGui.SetNextWindowCached(key)
|
||||
local g = GImGui
|
||||
ImGui.SetNextWindowRefreshPolicy(bit32.bor(ImGuiWindowRefreshFlags.TryToAvoidRefresh, ImGuiWindowRefreshFlags.RefreshOnHover, ImGuiWindowRefreshFlags.RefreshOnFocus))
|
||||
g.NextWindowData.CacheKeySet = true
|
||||
g.NextWindowData.CacheKeyVal = key
|
||||
end
|
||||
|
||||
function ImGui.GetWindowDpiScale()
|
||||
@@ -10827,6 +10861,10 @@ function ImGui.End()
|
||||
IM_ASSERT(window.DrawList == nil)
|
||||
window.DrawList = window.DrawListInst
|
||||
end
|
||||
if window.CacheSkipItemsBackup ~= nil then -- [Port] SetNextWindowCached()
|
||||
window.SkipItems = window.CacheSkipItemsBackup
|
||||
window.CacheSkipItemsBackup = nil
|
||||
end
|
||||
|
||||
-- Stop logging
|
||||
if g.LogWindow == window then -- FIXME: add more options for scope of logging
|
||||
@@ -35105,7 +35143,9 @@ function ImGuiNextWindowData()
|
||||
SizeCallbackUserData = nil,
|
||||
BgAlphaVal = nil,
|
||||
MenuBarOffsetMinVal = ImVec2(),
|
||||
RefreshFlagsVal = nil
|
||||
RefreshFlagsVal = nil,
|
||||
CacheKeySet = false, -- [Port] SetNextWindowCached()
|
||||
CacheKeyVal = nil
|
||||
}, IMGUI_NEXT_WINDOW_DATA)
|
||||
end
|
||||
|
||||
@@ -36277,6 +36317,8 @@ function ImGuiWindow(ctx, name)
|
||||
SkipItems = false,
|
||||
|
||||
SkipRefresh = false,
|
||||
CacheSnap = nil, -- [Port] SetNextWindowCached(): state at the last refresh
|
||||
CacheSkipItemsBackup = nil,
|
||||
|
||||
Appearing = false,
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ KNOWN ISSUES
|
||||
|
||||
PERFORMANCE IDEAS (not done)
|
||||
----------------------------
|
||||
- Opt-in cached windows (skip user code for idle windows).
|
||||
- Cheaper visible ColorEdit4 rows; cheaper Begin() for child windows.
|
||||
|
||||
PLANNED EXTENSIONS
|
||||
|
||||
@@ -8846,6 +8846,26 @@ function ImGui.UpdateWindowSkipRefresh(window)
|
||||
end
|
||||
|
||||
if bit32.band(g.NextWindowData.RefreshFlagsVal, ImGuiWindowRefreshFlags.TryToAvoidRefresh) ~= 0 then
|
||||
-- [Port] SetNextWindowCached(): refresh on any change since the last refresh (and one frame after being busy)
|
||||
if g.NextWindowData.CacheKeySet then
|
||||
local function related(w) return w ~= nil and (w.RootWindow == window.RootWindow or ImGui.IsWindowWithinBeginStackOf(w.RootWindow, window)) end
|
||||
local busy = window.Appearing or window.Hidden or window.DockIsActive or related(g.HoveredWindow) or related(g.NavWindow) or related(g.ActiveIdWindow) or (g.MovingWindow ~= nil and related(g.MovingWindow))
|
||||
local c, key, ds, tex = window.CacheSnap, g.NextWindowData.CacheKeyVal, g.IO.DisplaySize, g.IO.Fonts.TexData
|
||||
if busy or c == nil or c.Busy or c.Key ~= key or c.Tex ~= tex or c.Collapsed ~= window.Collapsed
|
||||
or c.PosX ~= window.Pos.x or c.PosY ~= window.Pos.y or c.SizeX ~= window.SizeFull.x or c.SizeY ~= window.SizeFull.y
|
||||
or c.ScrollX ~= window.Scroll.x or c.ScrollY ~= window.Scroll.y or window.ScrollTarget.x < FLT_MAX or window.ScrollTarget.y < FLT_MAX
|
||||
or c.DisplayX ~= ds.x or c.DisplayY ~= ds.y then
|
||||
c = c or {}
|
||||
window.CacheSnap = c
|
||||
c.Busy, c.Key, c.Tex, c.Collapsed = busy, key, tex, window.Collapsed
|
||||
c.PosX, c.PosY, c.SizeX, c.SizeY = window.Pos.x, window.Pos.y, window.SizeFull.x, window.SizeFull.y
|
||||
c.ScrollX, c.ScrollY, c.DisplayX, c.DisplayY = window.Scroll.x, window.Scroll.y, ds.x, ds.y
|
||||
return
|
||||
end
|
||||
-- skip: widgets early-out on SkipItems and Begin() returns visible=false; End() restores it
|
||||
window.CacheSkipItemsBackup = window.SkipItems
|
||||
window.SkipItems = true
|
||||
end
|
||||
-- FIXME-IDLE: Tests for e.g. mouse clicks or keyboard while focused.
|
||||
if window.Appearing then -- If currently appearing
|
||||
return
|
||||
@@ -9477,6 +9497,20 @@ function ImGui.SetNextWindowRefreshPolicy(flags)
|
||||
local g = GImGui
|
||||
g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasRefreshPolicy)
|
||||
g.NextWindowData.RefreshFlagsVal = flags
|
||||
g.NextWindowData.CacheKeySet = false
|
||||
end
|
||||
|
||||
-- [Port] Opt-in cached window (built on the refresh policy above). While nothing that could change the window
|
||||
-- happened, Begin() keeps last frame's draw list and returns visible=false, so `if visible then ... end` skips your code.
|
||||
-- It refreshes when `key` changes (pass e.g. a version number of the data shown), while the window (or a popup/child
|
||||
-- of it) is hovered, focused or active and for one frame after, and when it appears, moves, resizes, scrolls,
|
||||
-- collapses, is docked, or the display size or font atlas texture changes.
|
||||
-- Anything else that changes the contents (style, fonts, time) must be part of `key`.
|
||||
function ImGui.SetNextWindowCached(key)
|
||||
local g = GImGui
|
||||
ImGui.SetNextWindowRefreshPolicy(bit32.bor(ImGuiWindowRefreshFlags.TryToAvoidRefresh, ImGuiWindowRefreshFlags.RefreshOnHover, ImGuiWindowRefreshFlags.RefreshOnFocus))
|
||||
g.NextWindowData.CacheKeySet = true
|
||||
g.NextWindowData.CacheKeyVal = key
|
||||
end
|
||||
|
||||
function ImGui.GetWindowDpiScale()
|
||||
@@ -10679,6 +10713,10 @@ function ImGui.End()
|
||||
IM_ASSERT(window.DrawList == nil)
|
||||
window.DrawList = window.DrawListInst
|
||||
end
|
||||
if window.CacheSkipItemsBackup ~= nil then -- [Port] SetNextWindowCached()
|
||||
window.SkipItems = window.CacheSkipItemsBackup
|
||||
window.CacheSkipItemsBackup = nil
|
||||
end
|
||||
|
||||
-- Stop logging
|
||||
if g.LogWindow == window then -- FIXME: add more options for scope of logging
|
||||
|
||||
+5
-1
@@ -1236,7 +1236,9 @@ function ImGuiNextWindowData()
|
||||
SizeCallbackUserData = nil,
|
||||
BgAlphaVal = nil,
|
||||
MenuBarOffsetMinVal = ImVec2(),
|
||||
RefreshFlagsVal = nil
|
||||
RefreshFlagsVal = nil,
|
||||
CacheKeySet = false, -- [Port] SetNextWindowCached()
|
||||
CacheKeyVal = nil
|
||||
}, IMGUI_NEXT_WINDOW_DATA)
|
||||
end
|
||||
|
||||
@@ -2408,6 +2410,8 @@ function ImGuiWindow(ctx, name)
|
||||
SkipItems = false,
|
||||
|
||||
SkipRefresh = false,
|
||||
CacheSnap = nil, -- [Port] SetNextWindowCached(): state at the last refresh
|
||||
CacheSkipItemsBackup = nil,
|
||||
|
||||
Appearing = false,
|
||||
|
||||
|
||||
+6
-1
@@ -39,6 +39,11 @@ def upstream_decls():
|
||||
return decls
|
||||
|
||||
LOOSE = "(...any) -> ...any"
|
||||
PORT_ONLY = { # port additions that aren't in imgui.h
|
||||
"SetNextWindowCached": "(key: any) -> ()",
|
||||
"NewTextBuffer": "(str: string, size: number?) -> {number}",
|
||||
"TextBufferToString": "(buf: {number}) -> string",
|
||||
}
|
||||
|
||||
def signature(port_params, ret, cparams):
|
||||
if any(p == "..." for p in cparams):
|
||||
@@ -103,7 +108,7 @@ def types_header(files):
|
||||
"export type ImGuiAPI = {"]
|
||||
for name in sorted(fns):
|
||||
d = decls.get(name)
|
||||
sig = signature(fns[name], *d[0]) if d and len(d) == 1 else LOOSE
|
||||
sig = PORT_ONLY.get(name) or (signature(fns[name], *d[0]) if d and len(d) == 1 else LOOSE)
|
||||
lines.append(" %s: %s," % (name, sig))
|
||||
lines += [" [string]: any,", "}"]
|
||||
for name, keys in sorted(enums(files).items()):
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
-- SetNextWindowCached: the same scripted scenario runs twice, window "Win##A" uncached then "Win##B" cached.
|
||||
-- Every frame the window's draw list must hash identically in both phases, while B's body runs far less often.
|
||||
-- run: python3 tools/test/run.py --main tools/test/scripts/t_cached_window.luau --frames 130 --quiet --roblox
|
||||
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||
local ImGui, ImVec2 = G.ImGui, G.ImVec2
|
||||
ImGui.CreateContext()
|
||||
G.ImGui_ImplRoblox.Init()
|
||||
G.ImGui_ImplRoblox.SetRenderRate(0)
|
||||
local io = ImGui.GetIO()
|
||||
local P = 60 -- frames per phase
|
||||
local frame, hashes, runs = 0, {}, { A = 0, B = 0 }
|
||||
|
||||
local function hash(dl)
|
||||
local h = 0
|
||||
local function mix(v) h = (h * 31 + math.floor(v * 64 + 0.5)) % 2147483647 end
|
||||
local vb, id = dl.VtxBuffer.Buf, dl.IdxBuffer.Data
|
||||
for i = 1, dl.VtxBuffer.Size do local o = (i - 1) * 36; mix(buffer.readf64(vb, o)); mix(buffer.readf64(vb, o + 8)); mix(buffer.readf64(vb, o + 16) * 4096); mix(buffer.readf64(vb, o + 24) * 4096); mix(buffer.readu32(vb, o + 32) / 64) end
|
||||
for i = 1, dl.IdxBuffer.Size do mix(id[i]) end
|
||||
for _, c in dl.CmdBuffer:iter() do mix(c.ClipRect.x); mix(c.ClipRect.y); mix(c.ClipRect.z); mix(c.ClipRect.w); mix(c.ElemCount) end
|
||||
return h
|
||||
end
|
||||
|
||||
game:GetService("RunService").Stepped:Connect(function()
|
||||
frame += 1
|
||||
if frame > 2 * P then return end
|
||||
local phase = frame <= P and "A" or "B"
|
||||
local t = (frame - 1) % P + 1 -- frame within the phase
|
||||
G.ImGui_ImplRoblox.NewFrame()
|
||||
io.DeltaTime = 1 / 60
|
||||
-- mouse: away, over the button (hover highlight), away again
|
||||
if t >= 20 and t < 26 then io:AddMousePosEvent(60, 75) else io:AddMousePosEvent(700, 500) end
|
||||
local key = math.floor(t / 15) -- data version: changes at t = 15, 30, 45
|
||||
ImGui.NewFrame()
|
||||
if t == 3 then ImGui.SetWindowFocus(nil) end -- focused -> unfocused title bar must not stay stale
|
||||
if t == 1 then ImGui.SetNextWindowFocus() end
|
||||
ImGui.SetNextWindowPos(ImVec2(20, 20), G.ImGuiCond.Always)
|
||||
ImGui.SetNextWindowSize(ImVec2(220, 140), G.ImGuiCond.Always)
|
||||
if phase == "B" then ImGui.SetNextWindowCached(key) end
|
||||
local _, visible = ImGui.Begin("Win##" .. phase)
|
||||
if visible then
|
||||
runs[phase] += 1
|
||||
ImGui.Button("Button")
|
||||
ImGui.Text("value %d", key)
|
||||
ImGui.SliderFloat("slider", key * 0.1, 0, 1)
|
||||
end
|
||||
ImGui.End()
|
||||
ImGui.Render()
|
||||
local h = hash(ImGui.FindWindowByName("Win##" .. phase).DrawList)
|
||||
if phase == "A" then hashes[t] = h
|
||||
elseif h ~= hashes[t] then error(("frame %d: cached draw list differs from uncached"):format(t)) end
|
||||
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||
if frame == 2 * P then
|
||||
print(("runs: uncached %d, cached %d"):format(runs.A, runs.B))
|
||||
assert(runs.A == P, "uncached window must run every frame")
|
||||
assert(runs.B < P / 3, "cached window ran too often")
|
||||
print("CACHED OK")
|
||||
end
|
||||
end)
|
||||
@@ -124,12 +124,24 @@ local function MyTool()
|
||||
end
|
||||
-- [/snippet]
|
||||
|
||||
-- [snippet:cached]
|
||||
local stats = { version = 0, kills = 0 }
|
||||
local function StatsWindow()
|
||||
ImGui.SetNextWindowCached(stats.version) -- bump stats.version whenever stats change
|
||||
local _, visible = ImGui.Begin("Stats")
|
||||
if visible then -- false while the cached contents are reused
|
||||
ImGui.Text("Kills: %d", stats.kills)
|
||||
end
|
||||
ImGui.End()
|
||||
end
|
||||
-- [/snippet]
|
||||
|
||||
local frame = 0
|
||||
RunService.Stepped:Connect(function()
|
||||
frame += 1
|
||||
G.ImGui_ImplRoblox.NewFrame()
|
||||
ImGui.NewFrame()
|
||||
MyFirstTool(); MyTool(); Conventions(); MenusAndTable(); Plot(); CustomDrawing()
|
||||
MyFirstTool(); MyTool(); Conventions(); MenusAndTable(); Plot(); CustomDrawing(); StatsWindow()
|
||||
ImGui.Render()
|
||||
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||
if frame == 20 then
|
||||
|
||||
Reference in New Issue
Block a user