SetNextWindowCached(key): opt-in cached windows on top of the refresh policy (skip contents while idle), t_cached_window test, README snippet

This commit is contained in:
4DBug
2026-10-08 20:43:56 -05:00
parent df4cf4cd80
commit 2cff3037ee
14 changed files with 241 additions and 10 deletions
+14
View File
@@ -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`):
+2
View File
@@ -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
+14
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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,
-1
View File
@@ -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
+38
View File
@@ -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
View File
@@ -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
View File
@@ -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()):
+58
View File
@@ -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)
+13 -1
View File
@@ -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