From 5e7cf0fc3aa120486c11bdea2ee4e3ff135160c4 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Sun, 22 Sep 2024 13:15:31 -0700 Subject: [PATCH] perf: Only trigger a state change if the state is definitely different --- src/card-controller/conditions-manager.ts | 12 ++++- .../conditions-manager.test.ts | 54 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/card-controller/conditions-manager.ts b/src/card-controller/conditions-manager.ts index ddce4544..cfa5d1bb 100644 --- a/src/card-controller/conditions-manager.ts +++ b/src/card-controller/conditions-manager.ts @@ -1,5 +1,6 @@ import { CurrentUser } from '@dermotduffy/custom-card-helpers'; import { HassEntities } from 'home-assistant-js-websocket'; +import { isEqual } from 'lodash-es'; import merge from 'lodash-es/merge'; import { ZodSchema } from 'zod'; import { @@ -10,10 +11,10 @@ import { } from '../config/management'; import { FrigateCardCondition, - RawFrigateCardConfig, - ViewDisplayMode, frigateConditionalSchema, Overrides, + RawFrigateCardConfig, + ViewDisplayMode, } from '../config/types'; import { desparsifyArrays } from '../utils/basic'; import { CardConditionAPI, KeysState } from './types'; @@ -227,6 +228,13 @@ export class ConditionsManager { } public setState(state: Partial): void { + // Performance: Compare the new state with the existing state and only + // trigger a change if the new state is different. Only the new keys are + // compared, since some of the values (e.g. 'state') will be large. + if (Object.keys(state).every((key) => isEqual(state[key], this._state[key]))) { + return; + } + this._state = { ...this._state, ...state, diff --git a/tests/card-controller/conditions-manager.test.ts b/tests/card-controller/conditions-manager.test.ts index aa1e9497..0f3e81a9 100644 --- a/tests/card-controller/conditions-manager.test.ts +++ b/tests/card-controller/conditions-manager.test.ts @@ -429,6 +429,60 @@ describe('ConditionsManager', () => { expect(manager.getState()).toEqual(state); }); + describe('should set state', () => { + it('should set and be able to get it again', () => { + const state = { + fullscreen: true, + }; + + const manager = new ConditionsManager(createCardAPI()); + + manager.setState(state); + expect(manager.getState()).toEqual(state); + }); + + it('should set but only trigger when necessary', () => { + const state_1 = { + fullscreen: true, + state: { + 'binary_sensor.foo': createStateEntity(), + }, + }; + + const listener = vi.fn(); + const manager = new ConditionsManager(createCardAPI(), listener); + + manager.setState(state_1); + expect(listener).toBeCalledTimes(1); + + manager.setState(state_1); + expect(listener).toBeCalledTimes(1); + + manager.setState({ fullscreen: true }); + expect(listener).toBeCalledTimes(1); + + manager.setState({ + state: { + 'binary_sensor.foo': createStateEntity(), + }, + }); + expect(listener).toBeCalledTimes(1); + + manager.setState({ fullscreen: false }); + expect(listener).toBeCalledTimes(2); + + manager.setState({ fullscreen: false }); + expect(listener).toBeCalledTimes(2); + + manager.setState({ + state: { + 'binary_sensor.foo': createStateEntity({ state: 'off' }), + }, + }); + expect(listener).toBeCalledTimes(3); + }); + }); + describe('should handle hasHAStateConditions', () => { beforeEach(() => { vi.spyOn(window, 'matchMedia').mockReturnValueOnce({