Commit graph

4 commits

Author SHA1 Message Date
Vadim Peretokin
f8eb6b5597
fix: saved variables holding functions come back empty again instead of half-filled (#9860)
#### Brief overview of PR changes/additions

- A saved variable with a function, userdata or coroutine anywhere
inside it now exports only the members registered in `savedVars`, which
for a table ticked while empty is an empty group. Tables holding nothing
but data keep the full save added in #9762.
- The save-time walk records which saved globals hold such a value;
`XMLexport` turns the ride-along off for those variables only. Silent,
and it costs nothing outside a save.
- New `SavedVariableFenceTest` pins all 11 measured shapes, on disk and
after a reload.

#### Motivation for adding to Mudlet

A half-restored table defeats the `if next(t) == nil then rebuild() end`
guard packages carry, which kills cron-daemon's scheduler permanently on
its first minute wake after a restart.

#### Other info (issues closed, discussion etc)

Fixes #9857

**Test case:** install cron-daemon, tick `cron` in the Variables view,
add a job with a `command` function, restart and wait for the minute
rollover - the daemon keeps running (measured 2/2 canary fires, against
0/2 before).

Assisted-by: Claude:claude-opus-5
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
2026-08-13 15:49:17 +02:00
Vadim Peretokin
6c2d2444b2
fix: profile saves silently dropping saved variables that a second global references (#9762)
#### Brief overview of PR changes/additions
- A profile save read a fresh variable tree by walking all of `_G`
first-seen-wins, so a saved table another global name reached first was
filed under that name and written nowhere. Saves now read only the
globals the profile saves, each in its own dedup scope, and a name the
user saves is never deduped away.
- Same change takes the save off the size of `_G`: 0.269 s -> 0.007 s
per save at 20,000 globals, 0.013 s -> 0.001 s on a default profile
(4.22.0 is 0.002 s).
- `iterateTable()` names the table when it stops at 99 levels of nesting
instead of handing back an empty one, and the save tells the user which
saved variables that leaves empty.

#### Motivation for adding to Mudlet
Silent, permanent data loss on every save with no user action: a stock
4.x profile with EMCO/AdjustableContainer packages lost 1416 of its 1444
saved variable entries on the first 5.0 session.

#### Other info (issues closed, discussion etc)
Fixes #9755. Keeps #9704's fix (quitting with the editor on the
Variables tab) intact - the export still builds a throwaway tree, so the
Variables editor's tree items are never stranded.

Measured on a real profile (`Pox`, fresh isolated HOME): 25 variables /
4 groups / 4.3 KB before, 2267 / 535 / 497 KB after, identical on a
second session. 4.22.0 wrote 1163 / 281 / 258 KB. The difference above
4.22.0 is the live EMCO and AdjustableContainer objects the profile
keeps inside its saved `demonnic` table, which the ride-along rule from
#9517 says to save.

**Test case:** `lua qaShared = {a = "alpha"}`, tick `qaShared` in the
editor's Variables view, then `lua aaaAliasOfShared = qaShared`, quit
and reopen - `qaShared.a` is still there. `ctest -R
XMLexportVariablesTest` covers it; 5 of the 8 new cases were verified to
fail against the unfixed source.
2026-08-12 21:27:46 +02:00
Vadim Peretokin
6c67a13826
fix: two ways a profile save could lose or resurrect your data (#9704)
#### Brief overview of PR changes/additions
- Variables: the export skipped its refresh whenever the editor's
Variables view was on screen, so anything a script wrote into a saved
variable while it sat open was dropped from the save - including the
session's last save, which is taken with whatever view the editor was
left on. The variables are now read into a throwaway tree, which also
stops a save stranding the editor's variable search results.
- Packages: a save taken while a unit was still executing an item of a
package that had just been uninstalled wrote that package's items back
into the profile, where they returned as orphans the Package Manager
could not remove. The XML writers now skip what the units have queued
for a deferred delete, the module writer included - reloading a module
from a script used to write both the pre- and post-reload copies of its
items into the module file.
- `LuaInterface::getVars()` is now `setjmp`-guarded like every other
Lua-touching method there, so a panic cannot jump past the export's
scope with its variable tree and registry references still held.

#### Motivation for adding to Mudlet
Both are silent data loss in everyday use: quitting with the editor on
the Variables tab, and the `mpkg`/auto-updater shape of uninstalling a
package from a script.

#### Other info (issues closed, discussion etc)
From the 5.0 QA sweep, findings C13 and C14. The variables half re-opens
the loss that `20009c5ec` "fix: variables added while playing are no
longer lost when saving (#9492)" fixed, via the guard it added; the
packages half is the missing counterpart to the self-uninstall deferral
in `276e8bbfd` (#9383) and its follow-ups. #9492's own cases still pass
unchanged.

**Test case:** create a table from the command line, tick it to be saved
in the editor's Variables view, leave the editor there, run `lua
myTable.later = "x"`, quit and reopen - `later` is still there. `ctest
-R 'XMLexportVariablesTest|PackageSelfUninstallTest'` covers both
halves; all 12 new cases were verified to fail against the unfixed
source.

Assisted-by: Claude:claude-opus-5
2026-08-07 10:14:45 +02:00
Vadim Peretokin
20009c5ecb
fix: variables added while playing are no longer lost when saving (#9492)
### Brief overview of PR changes/additions

Fixes silent data loss where saved variables created at runtime could
vanish from a profile on save.

When exporting a profile, `XMLexport::writeVariablePackage` reused the
`VarUnit` variable tree that was built once at profile load. That tree
is only ever (re)built at profile load and when the Variables editor
populates it, so any variable a script created afterwards was absent
from the tree and silently dropped from the saved profile - even when it
was marked to be saved. Members a script added to an already-saved table
at runtime were dropped for a second reason: only members individually
recorded as saved were exported. A table marked saved now exports its
members as they exist at save time, recursively - except hidden ones
(Mudlet's internals stay out of the XML) and unsaveable ones (functions,
references, oversized tables).

The export now refreshes the variable tree so it reflects the current
Lua state before writing it out. To avoid the refresh disrupting the
Variables editor when it is open (rebuilding the tree there would clear
the widget the user is interacting with, silently breaking selections
and save checkboxes), the refresh is skipped only while that editor view
is actually on screen - in that case the editor already owns and keeps
the tree current.

Changes:
- `src/XMLexport.cpp` - refresh the `VarUnit` tree before export,
guarded so an on-screen Variables editor is not rebuilt out from under
the user; export members of saved tables as they exist at save time.
- `src/dlgTriggerEditor.{h,cpp}` - small `variablesViewActive()`
accessor used by the guard.
- `test/functional_tests/XMLexportVariablesTest.cpp` - fail-first
functional tests: a saved variable created after the tree was built is
exported (name and value), an unsaved one is not, and hidden-variable
preferences are still written. Members a script adds to an already-saved
table at runtime (issue #9517) are now saved with the table - including
string-, numeric- and nested-table members - while members of unsaved
tables, hidden members, function members, reference-keyed members and
tables over the 10,000-item limit stay out of the XML. All verified to
fail on the unfixed code.

### Motivation for adding to Mudlet

Users whose scripts create variables at runtime and mark them to be
saved could lose that data on the next save without any warning, which
is a serious silent-data-loss bug for profiles.

### Have you tested this? If so, how?

Added functional tests (`XMLexportVariablesTest`, 15 cases) that
reproduce the losses against the current code and pass with the fix.
Also ran `ResetProfileTest` (full profile save/restore round-trip) and
`EnableDisableByNameTest` as regression checks - all pass.

Fixes #9517

Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-fable-5
2026-07-29 17:52:00 +02:00