Repository: dermotduffy/frigate-hass-card
Pull request #1569 (2024-09-22 20:15:40)

perf: Only trigger a state change if the state is definitely different


Patch:
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<ConditionState>): 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,


Test patch:
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({