Skip to content

Fix migration, editor, import and teardown bugs; drop dead code; save config on change - #59

Open
adobloug wants to merge 2 commits into
Haplo064:masterfrom
adobloug:fix/correctness-and-cleanup
Open

adobloug wants to merge 2 commits into
Haplo064:masterfrom
adobloug:fix/correctness-and-cleanup

Conversation

@adobloug

Copy link
Copy Markdown

Fixes several latent defects, removes dead code, and replaces the frame-counter autosave with save-on-change. No intended change to the shapes users see on screen.

These changes have not been compiled. No .NET SDK or Dalamud assemblies were available on the machine they were written on, so everything below was verified by static inspection only. Please build before merging.

Correctness

  • Unbalanced ImGui window stack. The first-run welcome window called ImGui.Begin with no matching ImGui.End.

  • Leaked event handler. The constructor subscribes UiBuilder.OpenMainUi += ConfigWindow, but Dispose unsubscribed OpenConfigUi. The live handler was never detached, so a plugin reload left the old delegate attached. Dispose now removes OpenMainUi. The gear-icon binding is deliberately left as-is, per 1dee8fc.

  • The v7 migration corrupted job selections. bool[] JobTemp = doodle.JobsBool; aliases the array rather than copying it, and the shift loop ran ascending, so each iteration read the value the previous one had just overwritten. Slots 15 through 20 (BRD, MCH, DNC, BLM, SMN, RDM) all ended up holding the old BRD value. The loop now runs descending, which makes the in-place shift correct, and the redundant alias is gone. The block is also guarded to only run on arrays of exactly the pre-7.0 length of 21, since it blind-indexes [20] and [22].

    Note this does not repair configs that already migrated. They have Version == 7 stored, so the block will not re-run, and the pre-shift state is unrecoverable. Affected users have to re-tick jobs 15 through 20 by hand.

  • Editor offsets accumulated across doodles. The player-center pixel was computed once outside the foreach over _doodleBag and then +='d inside it by the ring, dot, and dashed-ring branches, without ever being reset. Doodle N rendered at the sum of every preceding doodle's offset, and the line-endpoint hit tests read the same drifting value, so dragging was mis-aimed once any offset doodle preceded a line. The anchor is now immutable and each iteration derives its own draw position from it. The variables were renamed (anchorX/anchorY for the fixed origin, drawX/drawY for the per-doodle position) so that any site missed during the split fails to build rather than silently drawing at the wrong pixel.

  • Clipboard import trusted its input. DeserializeObject<List<Drawing>> returns null for the input "null", and AddRange(null) then threw. Imported JobsBool arrays also carried the exporter's length, and a pre-7.0 export has 21 entries where everything downstream assumes 23, which overran _doodleJobs and _doodleJobsUint on a later frame, outside the try. Import now null-guards, drops null entries, and normalizes every imported drawing.

  • Bounds in the per-frame path. CheckJob walked jobList while indexing _doodleJobsUint unbounded. It is now an indexed loop bounded by the shorter of the two. The job checkbox grid had the same shape and got the same treatment. A new NormalizeJobs helper pads or truncates JobsBool to the current job-table length, and runs both on config load and on import.

Cleanup

  • Dropped AssemblyVersion from PixelPerfect.json. DalamudPackager fills it from the assembly and overwrites whatever is in the file, so it was a second source of truth that had already drifted from the csproj.
  • Removed unused usings, the unreferenced IdToJob method and JobIds enum, dead locals in the doodle tab, and the debug readout that printed raw target coordinates into the Cone tab.
  • Hoisted the Occupied38 condition check out of the per-doodle loop into an early return.

Save on change

The config previously saved every 100 frames while the window was open, whether or not anything had changed, and edits made in the last stretch before exit were lost. Add, delete, and reorder never saved at all.

Each mutating widget now sets a dirty flag from its own return value, and the config is written once per frame when that flag is set and no widget is active. Gating on ImGui.IsAnyItemActive() coalesces a slider drag into a single write on release. The editor's endpoint drag sets the flag on mouse release rather than per frame, for the same reason. The explicit save on the Close button is unchanged.

Testing

Static checks only: ImGui Begin/End, PushStyleVar/PopStyleVar, PushItemWidth/PopItemWidth, PushStyleColor/PopStyleColor, Columns, and TreeNode/TreePop all pair on every path; every UiBuilder subscription has a matching unsubscription; the migration shift was simulated against a known bit pattern in both directions; every removed symbol has zero remaining references.

Worth exercising in game before merge: the welcome window on a fresh config, two offset doodles rendering independently in the editor, line-endpoint dragging landing under the cursor, importing a pre-7.0 export string, upgrading a pre-7 config with jobs ticked, reloading via /xlplugins without a duplicate overlay, and edits persisting after closing the config without pressing Close.

adobloug and others added 2 commits August 19, 2026 10:33
Five defects, none of which required new behaviour to surface:

- v7 job migration aliased its own array. `bool[] JobTemp = doodle.JobsBool`
  is a reference, so the ascending shift read slots it had already written
  and smeared old index 14 (BRD) across MCH, DNC, BLM, SMN and RDM. Now
  shifts descending in place. Configs already stamped Version 7 are past
  the point of repair; this only protects users still on a pre-7 config.
- The migration also blind-indexed [20] and [22], throwing on any stored
  array shorter than 21. Guarded on the expected length, and the v4 block
  is bounds- and null-checked the same way.
- Dispose removed OpenConfigUi while the constructor subscribed OpenMainUi,
  so the live handler leaked across reloads. The stale commented-out
  OpenConfigUi line goes with it; the gear icon stays deliberately inert.
- The first-run welcome window called ImGui.Begin with no matching End.
- Clipboard import trusted foreign JSON: a null document threw inside
  AddRange, and a pre-7.0 export carries 21 job flags where everything
  downstream assumes 23, overrunning _doodleJobs and _doodleJobsUint on a
  later frame — outside the try, so the catch never saw it. Imports are
  now null-filtered and normalised, as is the config load path.

The editor drew every doodle at the running sum of all preceding offsets,
because dotPosX/dotPosY were declared outside the loop and only ever
accumulated. The anchor is now immutable and each doodle offsets a local
copy, which also un-skews the line-endpoint hit tests.

Not compiled: no .NET SDK or Dalamud assemblies were available.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cleanup:

- Remove usings that nothing references: the stray JSType import, the
  unused Num and Condition aliases, Lumina.Excel.Sheets, System.Linq,
  Dalamud.Interface, Dalamud.Interface.Windowing, and Dalamud.Game.Gui
  (IGameGui is declared in Dalamud.Plugin.Services). Dalamud.Game and
  Dalamud.Game.ClientState are left alone — grep says dead, but that
  cannot be confirmed without compiling.
- Remove IdToJob and the JobIds enum. Neither has a caller; the live job
  mapping is _doodleJobsUint. Drawing.Job stays, since it is serialised
  config the v4 migration still reads.
- Remove the target coordinate and atan readouts left in the Cone tab.
- Remove the `vector` and `job` locals, both overwritten a line later.
- Drop AssemblyVersion from the manifest. DalamudPackager fills it from
  the assembly and overwrites whatever is there, which is how it drifted
  to 3.3.3.0 against the csproj's 3.3.4.0 in the first place.
- Hoist the Occupied38 cutscene check out of the per-doodle loop.

Saving:

The config was written every 100 frames while the window was open,
regardless of whether anything changed, and edits in the last <100 frames
were lost if the game exited. Add Doodle, Delete, reorder and the editor's
endpoint drag never saved at all.

Each widget now reports its own change through its return value, and a
single flush runs once per frame gated on IsAnyItemActive, so a drag
coalesces into one write on release. The editor marks dirty when an
endpoint is dropped rather than on every frame it is held, since a canvas
drag leaves no ImGui item active and would otherwise write every frame.

The reorder branch's early return is now an if/else so the flush stays
reachable on that path.

Not compiled: no .NET SDK or Dalamud assemblies were available.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant