USB-Meter / Documentation / External Contributions.md
1 contributor
199 lines | 9.567kb

External Contributions

This document tracks contributions made by collaborators outside the core project. Its purpose is to support ongoing evaluation of each contributor's work quality, patterns, and reliability over time.


Contributor A — anonim, prieten al autorului

Toate intervențiile cunoscute sunt în AppData.swift, ChargeInsightsStore.swift, și Meter.swift.


Intervenție 1 — Fix lag UI la formulare (aprilie 2026)

Context: La conectarea unui contor live, formularele din UI (typing, picker) deveneau iresponsive. reloadChargedDevices() rula sincron pe main thread la fiecare snapshot BLE (~1/s), blocând UIKit.

Commit: 4f79218 (înglobat cu Intervenția 2)

Modificări introduse:

  • chargeInsightsReadStore — context separat de citire (privateQueueConcurrencyType)
  • chargedDevicesReloadQueue — fetch mutat pe background queue
  • chargedDevicesReloadGeneration — counter de invalidare pentru fetch-uri async depășite

Bug introdus (descoperit ulterior):

Pattern-ul generation+invalidare era prea agresiv. Contorul era incrementat la fiecare apel reloadChargedDevices(), inclusiv cele declanșate în burst de NSManagedObjectContextObjectsDidChange. Orice fetch async dura >0ms → generația era deja depășită la finalizare → rezultatul era aruncat. Lista rămânea goală permanent atât timp cât un contor live era conectat, indiferent că SQLite conținea date reale.

Evaluare: Problema identificată era reală. Soluția era funcțională în absența unui contor conectat (test static), dar s-a rupt sub load real. Eroare clasică de async invalidation, probabil netestat cu dispozitiv real.


Intervenție 2 — Fix starvation reload (după aprilie 2026)

Context: Lista de charged devices rămânea goală permanent când un contor era activ. Cauzat de bug-ul din Intervenția 1. Autorul a identificat și corectat singur problema.

Commit: 4f79218

Modificări introduse:

  • Înlocuire generation counter cu pattern inFlight/pending
    • chargedDevicesReloadInFlight: Bool — un singur fetch în zbor la un moment dat
    • chargedDevicesReloadPending: Bool — cerere nouă marcată pentru după finalizare
  • scheduleChargedDevicesReload(delay: 0.15) — debounce pentru burst-uri de notificări

Evaluare: Corect și complet. Soluția standard pentru problema dată. Nu introduce regresii. Diagnosticul era precis (cita dimensiunile exacte din SQLite: 10 devices, 15 sesiuni, 49 checkpoints, 24.279 samples).


Intervenție 3 — Optimizare CPU pe Catalyst (aprilie 2026)

Context: Procesul Catalyst consuma CPU ridicat și constant. Identificat prin profiling.

Commits: 406be0e (împreună cu ajustări ulterioare ale intervalelor)

Cauze identificate de contributor:

  1. BLE polling continuu — dataDumpRequest() recursiv imediat după fiecare răspuns
  2. Fiecare pachet BLE atingea CoreData/iCloud direct, cu reload UI frecvent
  3. noteMeterSeen scria în MeterNameStore la fiecare observație

Modificări introduse:

  • Meter.swiftminimumLivePollingInterval = 0.4s; înlocuit recursie imediată cu scheduleNextLiveDataDumpRequest() care respectă intervalul minim
  • AppData.swift — coalescere snapshot-uri BLE în memorie (pendingChargeObservationSnapshots); flush periodic la 30s cu flush explicit la pause/stop/checkpoint/terminate; throttle noteMeterSeen la 15s; observer schimbat din ObjectsDidChangeDidSave
  • AppData.swiftwriteContext privat pentru ChargeInsightsStore (anterior store-ul scria direct pe viewContext, i.e. main thread)
  • ChargeInsightsStore.swiftmaximumLiveIntegrationGap 20s → 90s (aliniat cu fereastra de coalescere de 30s)

Probleme rămase după livrare (rezolvate în același commit):

  • Work item-ul de flush era dispatchat pe DispatchQueue.main; apela context.performAndWait → bloca main thread la fiecare 30s (spike vizibil în profiler)
  • flushPendingChargeObservation chema explicit reloadChargedDevices(), iar observer-ul DidSave chema și el scheduleChargedDevicesReload() — double reload per flush

Aceste două probleme au fost remediate separat (fix în 406be0e): flush-ul periodic mută scrierea CoreData pe DispatchQueue.global(qos: .utility); reload-ul explicit eliminat din flush, lăsat exclusiv pe seama observer-ului DidSave.

Note privind originea problemelor rezolvate:

NSManagedObjectContextObjectsDidChange ca observer și viewContext pentru scrieri existau în codul proiectului înainte de orice intervenție a acestui contributor. Intervenția 3 rezolvă probleme de design preexistente, nu regresii proprii. Singura auto-corecție este maximumLiveIntegrationGap, ajustat pentru coalescing-ul introdus tot în această intervenție.

Evaluare: Diagnostic corect pentru toate cele trei cauze. Modificările sunt coerente între ele. Livrarea conținea două bug-uri minore (main thread + double reload) care au necesitat remediere imediată, detectabile printr-un review atent al codului fără a fi nevoie de runtime. Efectul net: CPU redus de la consum constant la 0–4% cu spike-uri reziduale la ~30s (CloudKit sync inerent).


Session Trim Feature — Buffer Restore Bug (pentru externalizare)

Contextul feature-ului

S-a implementat un feature de "Session Trim": detectare automată a ferestrei reale de încărcare (ex: Apple Watch se încarcă 3h dar sesiunea rulează 16h overnight) + editor manual pentru trim. Feature-ul este complet implementat și compilează fără erori.

Există un bug pre-existent care blochează funcționarea feature-ului și trebuie rezolvat separat.


Bug: istoricul sesiunii se pierde la fiecare restart de aplicație

Simptom: La restart cu sesiune activă, graficul afișează intervalul complet al sesiunii (ex: 16h) dar cu date doar din momentul reconectării BLE. Istoricul din Core Data nu este restaurat.

Consecință pentru trim: Banner-ul de trim nu apare (se bazează pe aggregatedSamples gol), editorul de trim are graficul gol.


Unde este problema

Meter.restoreChargeRecordIfNeeded(from:)USB Meter/Model/Meter.swift

func restoreChargeRecordIfNeeded(from activeSession: ChargeSessionSummary) {
    guard chargeRecordState == .waitingForStart else { return }  // ← problema
    guard chargeRecordStartTimestamp == nil else { return }
    guard chargeRecordAH == 0, chargeRecordWH == 0, chargeRecordDuration == 0 else { return }

    measurements.restorePersistedChargeSessionSamplesIfNeeded(from: activeSession)
    ...
}

Race condition: Datele BLE ajung înaintea completării reloadChargedDevices (async). La primul pachet BLE, chargeRecordState trece waitingForStart → active. Când reloadChargedDevices finalizează și încearcă restore, primul guard blochează — permanent pentru sesiunea curentă.

Measurements.restorePersistedChargeSessionSamplesIfNeeded are propriul guard: returnează dacă vreun series este non-gol. Dacă se rezolvă problema de mai sus și datele BLE au umplut deja buffer-ul, acesta trebuie resetat înainte de restore — dar numai dacă aggregatedSamples nu este gol, altfel graficul rămâne gol definitiv.


Lanțul de apel la pornire

SceneDelegate.activateCloudDeviceSync
  → AppData.activateChargeInsights        // setup writeContext + readContext
  → AppData.scheduleChargedDevicesReload  // async, ~100–500ms

[BLE se conectează, date ajung — pe main queue]
  → Meter.processData → Meter.updateChargeRecord
    → chargeRecordState = .active  // ← guard-ul din restore va bloca

[reloadChargedDevices completează]
  → AppData.restoreChargeMonitoringStateIfNeeded(for: meter)
    → Meter.restoreChargeRecordIfNeeded(from: session)  // ← guard blochează

Fișiere modificate de feature (necommitted, diff disponibil)

Fișier Modificare
USB Meter/Model/CKModel.xcdatamodeld/USB_Meter 15.xcdatamodel/ Nou — adaugă trimStart/trimEnd opționale pe ChargeSession
USB Meter/Model/CKModel.xcdatamodeld/.xccurrentversion → v15
USB Meter/Model/ChargingWindowDetector.swift Nou — detectează fereastra activă de încărcare
USB Meter/Views/Meter/Tabs/ChargeRecord/SessionTrimEditorView.swift Nou — editor trim cu drag handles
USB Meter/Model/ChargeInsightsModel.swift trimStart, trimEnd, isTrimmed pe ChargeSessionSummary
USB Meter/Model/ChargeInsightsStore.swift setSessionTrim(sessionID:start:end:) — recalculează energie, șterge checkpoints
USB Meter/Model/AppData.swift Wrapper setSessionTrim
USB Meter/Views/Meter/Tabs/ChargeRecord/MeterChargeRecordTabView.swift Banner detecție + sheet editor
USB Meter.xcodeproj/project.pbxproj Referințe fișiere noi

Tipare observate

Interv. 1 Interv. 2 Interv. 3
Diagnostic corect Da Da Da
Funcționează fără load real Da Da Da
Funcționează sub load real Nu Da Parțial
Auto-corecție necesară Da (proprie) Parțial (externă)
Regresii introduse Da (starvation) Nu Nu

Observație recurentă: Contribuțiile funcționează corect în condiții statice dar au tendința să rateze edge case-uri sub load real (contor live conectat). Testele înainte de livrare par să nu includă scenariul cu dispozitiv activ.