mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 06:38:57 +00:00
perf(led): the led-strips-v1 profile proves caches, disconnect and a late import (#780)
r1 M5: the runtime and field chunks release a card's frame and field cache on disconnect (ledRelease from disconnectedCallback), never cache for a disconnected card, and a chunk that lands after disconnect renders nothing (the card's LED hook is connected-only). The profile now reports the three caches of the shown space separately (shapes ≤ 50, visibility ≤ 50, retained per-emitter fans ≤ 2500) through the runtime's ledStats, asserts 0 retained entries and 0 live LED timers/frames/observers after every disconnect, judges the Long Tasks of a 100-step camera series as well as the interaction profile's camera scenario, runs one extra cold mount whose runtime response is held while the card is removed, and enforces ≥ 7 samples after ≥ 1 warm-up — also on reports merged from parts (--warmup-only, --merge). Issue: #780 User-Visible: no
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
* 2) LEGACY fallback — baked-in country-house data (src/data/*), coordinates in a 1489×1053 canvas.
|
||||
* The icon layout is stored on the server (houseplan/layout/*), fallback — localStorage.
|
||||
*/
|
||||
import { ledAnchor, ledRuntime, ledStripsByMarker, ledVisible } from './led-strip-gate';
|
||||
import { ledAnchor, ledRelease, ledRuntime, ledStripsByMarker, ledVisible } from './led-strip-gate';
|
||||
import { ledButton, ledEditorFor, ledHistory, ledImportNote, ledSection, ledWallsNote } from './led-strip-card';
|
||||
import { LitElement, html, svg, nothing, noChange, TemplateResult, PropertyValues, type PropertyDeclaration } from 'lit';
|
||||
import { cache as litCache } from 'lit/directives/cache.js';
|
||||
@@ -2772,6 +2772,7 @@ export class HouseplanCard extends LitElement {
|
||||
this._continuityHistory = [...this._continuityHistory, ...this._continuity.trace].slice(-80);
|
||||
this._continuity.dispose();
|
||||
this._continuityDisposed = true;
|
||||
ledRelease(this); // #780 r1 M5: no retained LED cache after disconnect
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
@@ -10509,8 +10510,9 @@ export class HouseplanCard extends LitElement {
|
||||
|
||||
/** #780: the lazy LED chunk when the space shows a strip (ТЗ §13.1). */
|
||||
private _ledRt(space: SpaceModel) {
|
||||
return ledVisible(space, this._renderDevices, this._mode === 'devices' && this._showAll, this._serverCfg?.markers)
|
||||
? ledRuntime(space.id, () => this.requestUpdate()) : null;
|
||||
// A disconnected card never applies the chunk (a late import, r1 M5).
|
||||
return this.isConnected && ledVisible(space, this._renderDevices, this._mode === 'devices' && this._showAll, this._serverCfg?.markers)
|
||||
? ledRuntime(space.id, () => this.isConnected && this.requestUpdate()) : null;
|
||||
}
|
||||
|
||||
/** #780: the Devices-editor LED tool, its own lazy chunk (src/led-strip-card.ts). */
|
||||
|
||||
@@ -96,6 +96,12 @@ export class LedFieldCache {
|
||||
return value;
|
||||
}
|
||||
clear(): void { this.entries.clear(); this.ids.clear(); this.space = ''; }
|
||||
/** Retained visibility fans (the per-emitter source cache, ТЗ §13.2: ≤ 2500). */
|
||||
get sources(): number {
|
||||
let n = 0;
|
||||
for (const value of this.entries.values()) for (const piece of value?.pieces || []) n += piece.clip?.length ?? 0;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
const pointsKey = (points: readonly number[][]): string =>
|
||||
@@ -234,6 +240,16 @@ export interface LedFieldInput {
|
||||
}
|
||||
|
||||
const fieldCaches = new WeakMap<object, LedFieldCache>();
|
||||
/** Disconnect (ТЗ §13.2): no retained entry of this owner. */
|
||||
export function releaseLedField(owner: object): void {
|
||||
fieldCaches.get(owner)?.clear();
|
||||
fieldCaches.delete(owner);
|
||||
}
|
||||
/** The performance witness: what this owner retains right now. */
|
||||
export function ledFieldStats(owner: object): { visibility: number; sources: number; recomputes: number } {
|
||||
const cache = fieldCaches.get(owner);
|
||||
return { visibility: cache?.size ?? 0, sources: cache?.sources ?? 0, recomputes: cache?.recomputes ?? 0 };
|
||||
}
|
||||
export function ledFieldCache(owner: object): LedFieldCache {
|
||||
let cache = fieldCaches.get(owner);
|
||||
if (!cache) {
|
||||
|
||||
@@ -89,6 +89,9 @@ export function ledRuntime(entry: string, ready: () => void): LedRuntime | null
|
||||
'__HOUSEPLAN_LED_RETRY_ASSET__', (module) => module.LED_RUNTIME_FINGERPRINT);
|
||||
}
|
||||
|
||||
/** Disconnect: release this card's LED caches if the chunk is here (ТЗ §13.2). */
|
||||
export function ledRelease(owner: object): void { runtimeSlot.module?.releaseLed(owner); }
|
||||
|
||||
/** The Devices-editor LED tool chunk (ТЗ §13.1): only on an explicit entry. */
|
||||
export function ledEditorModule(entry: string, ready: () => void): LedEditorModule | null {
|
||||
return lazy(editorSlot, entry, ready, () => import('./led-strip-editor'),
|
||||
|
||||
@@ -413,6 +413,8 @@ export function ledFrame(input: LedFrameInput): LedFrame {
|
||||
// (ТЗ §13.1). The host is the card itself, read structurally.
|
||||
|
||||
export interface LedCardHost {
|
||||
/** A late chunk never re-renders (and re-fills caches of) a disconnected card. */
|
||||
isConnected?: boolean;
|
||||
_renderDevices: readonly DevItem[];
|
||||
_renderPlanHass: any; // any-ok: the card's HA snapshot type is internal to the card
|
||||
_virtualLights: VirtualLightSnapshot;
|
||||
@@ -443,6 +445,18 @@ export interface LedCardHost {
|
||||
|
||||
const frames = new WeakMap<object, { key: unknown[]; frame: LedFrame }>();
|
||||
|
||||
/** Disconnect (ТЗ §13.2, r1 M5): the frame and the field caches of this card are released. */
|
||||
export function releaseLed(owner: object): void {
|
||||
frames.delete(owner);
|
||||
field?.releaseLedField(owner);
|
||||
}
|
||||
|
||||
/** The performance witness: shapes (frame), visibility entries and retained fans of this card. */
|
||||
export function ledStats(owner: object): { shapes: number; visibility: number; sources: number; recomputes: number } {
|
||||
return { shapes: frames.get(owner)?.frame.views.length ?? 0,
|
||||
...(field?.ledFieldStats(owner) ?? { visibility: 0, sources: 0, recomputes: 0 }) };
|
||||
}
|
||||
|
||||
/** The LED frame of a space for this card, rebuilt only when an input changed. */
|
||||
export function ledFrameFor(host: LedCardHost, space: SpaceModel, spaceGlow: boolean): LedFrame {
|
||||
const polygons = space.rooms.flatMap((room) => {
|
||||
@@ -472,7 +486,8 @@ export function ledFrameFor(host: LedCardHost, space: SpaceModel, spaceGlow: boo
|
||||
inRoom: (point, room) => host._pointInRoom(point, room),
|
||||
showHidden: host._mode === 'devices' && host._showAll,
|
||||
});
|
||||
frames.set(host, { key, frame });
|
||||
// A disconnected card keeps nothing (a pending update after disconnect, r1 M5).
|
||||
if (host.isConnected !== false) frames.set(host, { key, frame });
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -524,8 +539,9 @@ export function renderLedLayerFor(
|
||||
}
|
||||
|
||||
export function renderLedFieldFor(host: LedCardHost, space: SpaceModel, spaceGlow: boolean): TemplateResult {
|
||||
if (host.isConnected === false) return svg`` as unknown as TemplateResult;
|
||||
const frame = ledFrameFor(host, space, spaceGlow);
|
||||
const module = frame.scene && fieldWanted(frame.views) ? ledField(space.id, () => host.requestUpdate()) : null;
|
||||
const module = frame.scene && fieldWanted(frame.views) ? ledField(space.id, () => host.isConnected !== false && host.requestUpdate()) : null;
|
||||
if (!module) return svg`` as unknown as TemplateResult;
|
||||
return module.renderLedField({
|
||||
views: frame.views,
|
||||
|
||||
Reference in New Issue
Block a user