#140 glass_layout.cfg lost every MFD line on a desktop teardown
Regression fromd213c98(the pod PadRIO/panel coupling fix). BTGlassPanels_Destroy calls SaveLayout FIRST, unconditionally, before it looks at whether any windows are left.d213c98added a SECOND caller (~LBE4ControlsManager) alongside the existing one in ~PadRIO, so on the desktop path both run: ~LBE4ControlsManager does `delete rioPointer`, which fires ~PadRIO -> destroy #1 saves the live windows and zeroes gWinCount -> destroy #2 then rewrites the whole file from an empty list. Why only the MFDs vanished, which is the detail that identifies it: external windows (plasma) already cached a last-known rect (gExtern[].haveLast) and were written from the cache; the per-display glass windows had no cache and were simply skipped once their HWND was gone. Hence the reported signature, "all the MFDs and secondary lines missing, but plasma was still there". The pod was never affected -- no PadRIO there, so only one destroy, and it runs BT_GLASS_LAYOUT=load off a frozen master regardless. Two fixes, because the guard alone would leave the trap armed for the next teardown-ordering change: 1. glass windows get the same remembered-geometry cache the extern windows have, kept ACROSS teardown. The file is now monotonic -- a save can update a line or add one, never drop one. 2. the teardown save is guarded on there being windows to report. Also corrects the comment at the L4CTRL call site, which claimed the second call was "a no-op on the PadRIO path". That claim is what made it look safe. Verified (scratchpad/night13/layoutsave.sh -- the tester's round trip, not a single launch, since the report was "saved fine, reset on relaunch"): run 1 one "saved 8 window position(s)" line (was two, the second wiping) cfg holds all 7 glass windows + plasma run 2 "restored 7 window position(s)", cfg byte-identical after the trip Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ
This commit is contained in:
co-authored by
Claude Opus 5
parent
6a96fb6420
commit
c04bec0a52
@@ -1118,11 +1118,22 @@ LBE4ControlsManager::~LBE4ControlsManager()
|
||||
|
||||
#ifdef BT_GLASS
|
||||
//
|
||||
// Symmetric with the Create above (2026-08-06). ~PadRIO tears the panels
|
||||
// down itself, so this is a no-op there -- but the HARDWARE RIO's dtor
|
||||
// knows nothing about them, and windows that outlive the surfaces they
|
||||
// blit are a crash waiting for the next mission cycle. Destroy is safe
|
||||
// if they were never created.
|
||||
// Symmetric with the Create above (2026-08-06). The HARDWARE RIO's dtor
|
||||
// knows nothing about the panels, and windows that outlive the surfaces
|
||||
// they blit are a crash waiting for the next mission cycle.
|
||||
//
|
||||
// CORRECTION (2026-08-07, #140): the original comment here claimed this was
|
||||
// "a no-op" on the PadRIO path because ~PadRIO tears the panels down itself.
|
||||
// It was not. ~PadRIO runs first (delete rioPointer, above), zeroing the
|
||||
// window list -- and BTGlassPanels_Destroy unconditionally ran SaveLayout
|
||||
// BEFORE looking at whether anything was left, so this second call rewrote
|
||||
// glass_layout.cfg with every MFD and radar line missing. Only the plasma
|
||||
// window survived, because external windows cached a last-known rect and the
|
||||
// per-display windows did not. That is the regression testers hit on the
|
||||
// desktop the same day the pod panels were wired up; the pod itself was
|
||||
// unaffected (no PadRIO, so only ONE destroy, and it runs BT_GLASS_LAYOUT=
|
||||
// load anyway). Fixed on both sides in L4GLASSWIN: the geometry is now
|
||||
// remembered across teardown, and the teardown save is guarded.
|
||||
//
|
||||
BTGlassPanels_Destroy();
|
||||
#endif
|
||||
|
||||
@@ -916,9 +916,50 @@ static void
|
||||
<< layoutFileName << "\n" << std::flush;
|
||||
}
|
||||
|
||||
// Write every window's current on-screen frame rect. Whole-file rewrite (it is
|
||||
// tiny), so partial/hard kills never leave a half-written file for long. Called
|
||||
// on finished-drag and on teardown in save mode.
|
||||
// LAST-KNOWN GEOMETRY, kept ACROSS teardown (2026-08-07, #140).
|
||||
//
|
||||
// SaveLayout rewrites the whole file, so it can only be as complete as what it
|
||||
// can see -- and it could only see LIVE windows. Destroy() nulls every hwnd and
|
||||
// zeroes gWinCount, so any save that ran after a teardown wrote a file with
|
||||
// every MFD and radar line MISSING. The plasma window survived that because
|
||||
// external windows already cached a last-known rect (gExtern[].haveLast); the
|
||||
// per-display windows had no such cache, which is exactly the reported
|
||||
// signature: "all the MFDs and secondary lines missing, but plasma was still
|
||||
// there". Give the glass windows the same guarantee, so the file is monotonic
|
||||
// -- a save can update a line or add one, never drop one.
|
||||
struct SavedGeom
|
||||
{
|
||||
char title[64];
|
||||
RECT r;
|
||||
int noFrame;
|
||||
};
|
||||
static SavedGeom gLastGeom[16];
|
||||
static int gLastGeomCount = 0;
|
||||
|
||||
static void
|
||||
RememberGeom(const char *title, const RECT &r, int noFrame)
|
||||
{
|
||||
if (title == NULL || title[0] == '\0')
|
||||
return;
|
||||
for (int i = 0; i < gLastGeomCount; ++i)
|
||||
if (strcmp(gLastGeom[i].title, title) == 0)
|
||||
{
|
||||
gLastGeom[i].r = r; gLastGeom[i].noFrame = noFrame;
|
||||
return;
|
||||
}
|
||||
if (gLastGeomCount >= (int)(sizeof(gLastGeom) / sizeof(gLastGeom[0])))
|
||||
return;
|
||||
SavedGeom &g = gLastGeom[gLastGeomCount++];
|
||||
strncpy(g.title, title, sizeof(g.title) - 1);
|
||||
g.title[sizeof(g.title) - 1] = '\0';
|
||||
g.r = r; g.noFrame = noFrame;
|
||||
}
|
||||
|
||||
// Write every window's frame rect. Whole-file rewrite (it is tiny), so
|
||||
// partial/hard kills never leave a half-written file for long. Called on
|
||||
// finished-drag and on teardown in save mode. Live windows refresh the
|
||||
// remembered geometry first; the FILE is then written from the remembered set,
|
||||
// so a window that has already been torn down keeps its line.
|
||||
static void
|
||||
SaveLayout()
|
||||
{
|
||||
@@ -945,6 +986,7 @@ static void
|
||||
"# this list too -- it can be dragged, remembered and set ,noframe.\n",
|
||||
f);
|
||||
int wrote = 0;
|
||||
// 1. refresh the remembered geometry from whatever is currently alive
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
{
|
||||
GWin &gw = gWins[i];
|
||||
@@ -953,10 +995,16 @@ static void
|
||||
RECT r;
|
||||
if (!GetWindowRect(gw.hwnd, &r))
|
||||
continue;
|
||||
fprintf(f, "%s=%ld,%ld,%ld,%ld%s\n", gw.title,
|
||||
(long)r.left, (long)r.top,
|
||||
(long)(r.right - r.left), (long)(r.bottom - r.top),
|
||||
gw.noFrame ? ",noframe" : ""); // keep the hand-added option
|
||||
RememberGeom(gw.title, r, gw.noFrame); // keep the hand-added option
|
||||
}
|
||||
// 2. write the remembered set -- including windows already torn down
|
||||
for (int i = 0; i < gLastGeomCount; ++i)
|
||||
{
|
||||
const SavedGeom &g = gLastGeom[i];
|
||||
fprintf(f, "%s=%ld,%ld,%ld,%ld%s\n", g.title,
|
||||
(long)g.r.left, (long)g.r.top,
|
||||
(long)(g.r.right - g.r.left), (long)(g.r.bottom - g.r.top),
|
||||
g.noFrame ? ",noframe" : "");
|
||||
++wrote;
|
||||
}
|
||||
// External windows (the plasma window) ride the same file. Cache the last
|
||||
@@ -1741,8 +1789,14 @@ void
|
||||
void
|
||||
BTGlassPanels_Destroy()
|
||||
{
|
||||
SaveLayout(); // backstop for a clean teardown (WM_EXITSIZEMOVE already
|
||||
// caught every finished drag); no-op unless mode==save
|
||||
// Backstop for a clean teardown (WM_EXITSIZEMOVE already caught every
|
||||
// finished drag); no-op unless mode==save. GUARDED on there being windows:
|
||||
// this function has TWO callers (~PadRIO and ~LBE4ControlsManager), so on
|
||||
// the desktop path it runs twice, and the second pass has nothing live to
|
||||
// report. The remembered-geometry cache above already makes that harmless,
|
||||
// but there is no reason to rewrite the file to say the same thing.
|
||||
if (gWinCount > 0)
|
||||
SaveLayout();
|
||||
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
# =========================================================================
|
||||
# #140 glass_layout.cfg save regression -- the FIELD composition.
|
||||
#
|
||||
# SAURON's report: "set my glass panels, saved and borders off ... relaunched
|
||||
# and it reset again ... the glass_layout.cfg had all the MFDs and secondary
|
||||
# lines missing, but plasma was still there".
|
||||
#
|
||||
# So the acceptance test is the full ROUND TRIP a tester does, not just a
|
||||
# single launch:
|
||||
# run 1 -- BT_GLASS_LAYOUT=save, panels come up, quit cleanly
|
||||
# check -- the cfg must list every MFD + the radar, not just plasma
|
||||
# run 2 -- relaunch; the panels must come back where they were
|
||||
#
|
||||
# The teardown is what mattered: BTGlassPanels_Destroy has TWO callers on the
|
||||
# desktop path (~PadRIO, then ~LBE4ControlsManager), and it ran SaveLayout
|
||||
# BEFORE checking whether any windows were left -- so the second pass rewrote
|
||||
# the file from an empty list.
|
||||
# =========================================================================
|
||||
set -x
|
||||
. /c/git/bt411/scratchpad/night6/bench_common.sh
|
||||
cd /c/git/bt411/content || exit 1
|
||||
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||
sleep 2
|
||||
rm -f glass_layout.cfg ls_run1.log ls_run2.log
|
||||
|
||||
# ---- run 1: create the layout ------------------------------------------
|
||||
( export BT_GLASS_PANELS=1 BT_GLASS_LAYOUT=save
|
||||
bt_launch ls_run1.log ARENA1.EGG 0x03 )
|
||||
sleep 55
|
||||
bt_kill_ours
|
||||
sleep 2
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||
sleep 3
|
||||
|
||||
echo "############ RUN 1 SAVE RESULT ############"
|
||||
echo "--- every 'saved N window position(s)' line (the double-save shows here) ---"
|
||||
grep -a "window position" ls_run1.log
|
||||
echo
|
||||
echo "--- glass_layout.cfg AFTER a clean quit ---"
|
||||
if [ -f glass_layout.cfg ]; then cat glass_layout.cfg; else echo "!!! NO FILE WRITTEN"; fi
|
||||
echo
|
||||
echo "--- line census (comments excluded) ---"
|
||||
echo -n "total entries : "; grep -acE "^[^#]+=" glass_layout.cfg 2>/dev/null
|
||||
echo -n "MFD lines : "; grep -acE "^(Heat|Comm|Mfd|Eng|Weap|Sec)" glass_layout.cfg 2>/dev/null
|
||||
echo -n "plasma line : "; grep -ac "Plasma" glass_layout.cfg 2>/dev/null
|
||||
cp glass_layout.cfg /tmp/ls_after_run1.cfg 2>/dev/null
|
||||
|
||||
# ---- run 2: does it RESTORE? -------------------------------------------
|
||||
( export BT_GLASS_PANELS=1 BT_GLASS_LAYOUT=save
|
||||
bt_launch ls_run2.log ARENA1.EGG 0x03 )
|
||||
sleep 55
|
||||
bt_kill_ours
|
||||
sleep 2
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||
sleep 3
|
||||
|
||||
echo "############ RUN 2 (RELAUNCH) ############"
|
||||
echo "--- restore receipts ---"
|
||||
grep -aE "restored|glass_layout|window position" ls_run2.log | head -12
|
||||
echo
|
||||
echo "--- cfg after run 2 -- must still hold every line ---"
|
||||
cat glass_layout.cfg 2>/dev/null
|
||||
echo
|
||||
echo "--- DIFF run1 -> run2 (empty = layout survived the round trip) ---"
|
||||
diff /tmp/ls_after_run1.cfg glass_layout.cfg && echo "IDENTICAL"
|
||||
Reference in New Issue
Block a user