Bundles several feature streams that have been iterating on the working tree: - Developer Tools page (Debug-only via DEVELOPER_TOOLS symbol): hosts the identification card, manual KWP write + transaction log, ROM/EEPROM dump card with progress banner and completion message, persisted custom-commands library, persisted EEPROM passwords library. New service primitives: IKwpService.SendRawCustomAsync / ReadEepromAsync / ReadRomEepromAsync. Persistence mirrors the Clients XML pattern in two new files (custom_commands.xml, eeprom_passwords.xml). - Auto-test orchestrator (IAutoTestOrchestrator + AutoTestState): linear K-Line read -> unlock -> bench-on -> test sequence with snackbar UI and progress dialog VM, gated on dashboard alarms. - BIP-STATUS display: BipDisplayViewModel + BipDisplayView, RAM read at 0x0106 via IKwpService.ReadBipStatusAsync; status definitions in BipStatusDefinition. - Tests page redesign: TestSectionCard + PhaseTileView replacing the old TestPlanView/TestRunningView/TestDoneView/TestPreconditionsView/ TestSectionView controls and their VMs. - Pump command sliders: Fluent thick-track style with overhang thumb, click-anywhere-and-drag, mouse-wheel adjustment. - Window startup: app.manifest declares PerMonitorV2 DPI awareness, MainWindow installs a WM_GETMINMAXINFO hook in OnSourceInitialized and maximizes there (after the hook is in place) so the app fits the work area exactly on any display configuration. - Misc: PercentToPixelsConverter, seed_aliases.py one-shot pump-alias importer, tools/Import-BipStatus.ps1, kline_eeprom_spec.md and dump-functions reference docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
59 lines
4.6 KiB
Markdown
59 lines
4.6 KiB
Markdown
# Gotcha: `OnPumpChanged` UI-thread jank — deferred fixes
|
||
|
||
## Status
|
||
**Deferred.** These two issues cause a small but measurable hitch on the UI thread during manual pump selection. For pumps without an immobilizer unlocker the jank is not perceptible enough to justify the work. Document kept so the cost is understood if we ever need to address it (e.g. if disk I/O gets slower, pump catalogs grow, or the jank stacks with a future UI change).
|
||
|
||
The separate unlocker-pump delay issue was fixed independently by making the unlock state observer event-driven via `CanBusParameter.ValueChanged`.
|
||
|
||
## Issue 1 — `ConfigurationService.LoadPumpStatus` does disk I/O on the UI thread
|
||
|
||
### Symptom
|
||
On pump change, up to ~20–200 ms of UI thread blocking (worst case on cold cache or spinning disk) before `OnPumpChanged` returns. Two XML parses per pump change.
|
||
|
||
### Why it happens
|
||
[`MainViewModel.OnPumpChanged`](../ViewModels/MainViewModel.cs) calls `_config.LoadPumpStatus(...)` twice (once for the `Status` word, once for `Empf3`) while building the status displays. [`LoadPumpStatus`](../Services/Impl/ConfigurationService.cs) caches by `statusId`, but on pump switch the new pump usually carries a different `Type`, so both lookups miss and trigger `XDocument.Load(StatusXml)` — a full file read plus XML parse. Runs synchronously on the UI thread.
|
||
|
||
### Fix (when needed)
|
||
Preload every `<PumpStatus>` entry into `_statusCache` during `ConfigurationService` construction (or app bootstrap). The file is small and status definitions never change at runtime. After that, `LoadPumpStatus` degenerates to a pure dictionary lookup and disk I/O is gone from the pump-change path.
|
||
|
||
Alternative (smaller change, slower fix): wrap both calls in `Task.Run` and apply results via `Dispatcher.InvokeAsync`. `PumpStatusDefinition` is a plain POCO, so it's safe to construct off-thread.
|
||
|
||
## Issue 2 — `TestPanelViewModel.LoadAllTests` allocation burst on the UI thread
|
||
|
||
### Symptom
|
||
~5–30 ms synchronous burst on pump change while the test panel is rebuilt. On slower machines or pumps with many tests the operator sees a visible hitch between clicking the pump and the page settling.
|
||
|
||
### Why it happens
|
||
[`LoadAllTests`](../ViewModels/TestPanelViewModel.cs) clears the `Tests` collection and synchronously creates one `TestSectionViewModel` per test definition, each spawning child `PhaseCardViewModel` and `GraphicIndicatorViewModel` instances. For a typical pump that's ~100+ view models constructed in a single tight loop, each raising `INotifyPropertyChanged` setters. The `ObservableCollection.Add` calls also dispatch `CollectionChanged` synchronously through any `ItemsControl` already bound to `Tests`.
|
||
|
||
### Fix (when needed)
|
||
In `OnPumpChanged`, yield before `LoadAllTests` so the already-queued render frame commits first:
|
||
|
||
```csharp
|
||
// ...all the lightweight synchronous bookkeeping (senders, CAN param swap,
|
||
// slider gate, unlock startup)...
|
||
|
||
// Let the frame with the slider-enable + pump-name update paint before
|
||
// we do the heavy test-panel rebuild.
|
||
await Dispatcher.Yield(DispatcherPriority.Background);
|
||
TestPanel.LoadAllTests(pump);
|
||
```
|
||
|
||
This turns `OnPumpChanged` into an `async void` handler — acceptable here because the caller is the `SelectedPump` partial-method hook which does not observe the returned Task. Operator sees the slider gate open and the unlock dialog appear instantly; the test panel fills in on the next dispatcher tick.
|
||
|
||
Alternative: keep `OnPumpChanged` synchronous and wrap only the rebuild in `Dispatcher.BeginInvoke(..., DispatcherPriority.Background)`. Same effect; easier to keep the void signature.
|
||
|
||
## Why we're not fixing these now
|
||
- Non-unlocker pumps: the combined ~25–230 ms worst case is absorbed by the operator's own reaction time after clicking the pump. Not flagged as a UX problem in field use.
|
||
- Unlocker pumps: the original 1 s unlock-dialog delay was the dominant visible symptom. That was fixed by the event-driven observer — the remaining jank from issues 1 and 2 sits under the noise floor of the unlock dialog appearing.
|
||
|
||
Revisit if:
|
||
- Pump catalogs grow to the point that `LoadAllTests` crosses the ~50 ms mark
|
||
- `status.xml` grows (new status types) or storage latency regresses
|
||
- Any future UI change on `DashboardPage` / `PumpPage` makes the pump-change transition visually tighter and exposes the hitch
|
||
|
||
## Files involved
|
||
- [ViewModels/MainViewModel.cs](../ViewModels/MainViewModel.cs) — `OnPumpChanged` (lines 391–470)
|
||
- [Services/Impl/ConfigurationService.cs](../Services/Impl/ConfigurationService.cs) — `LoadPumpStatus` (lines 364–435)
|
||
- [ViewModels/TestPanelViewModel.cs](../ViewModels/TestPanelViewModel.cs) — `LoadAllTests` (lines 110–125)
|