Skip to content

Commit 36e2baf

Browse files
authored
fix: Use Map for State (#27105)
1 parent 6e34607 commit 36e2baf

File tree

16 files changed

+89
-85
lines changed

16 files changed

+89
-85
lines changed

lib/eventBus.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type EventBusListener<K> = K extends keyof EventBusMap
3535
: never;
3636

3737
export default class EventBus {
38-
private callbacksByExtension: Map<string, {event: keyof EventBusMap; callback: EventBusListener<keyof EventBusMap>}[]> = new Map();
38+
private callbacksByExtension = new Map<string, {event: keyof EventBusMap; callback: EventBusListener<keyof EventBusMap>}[]>();
3939
private emitter = new events.EventEmitter<EventBusMap>();
4040

4141
constructor() {

lib/extension/availability.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ const RETRIEVE_ON_RECONNECT: readonly {keys: string[]; condition?: (state: KeyVa
2020

2121
export default class Availability extends Extension {
2222
/** Mapped by IEEE address */
23-
private readonly timers: Map<string, NodeJS.Timeout> = new Map();
23+
private readonly timers = new Map<string, NodeJS.Timeout>();
2424
/** Mapped by IEEE address or Group ID */
25-
private readonly lastPublishedAvailabilities: Map<string | number, boolean> = new Map();
25+
private readonly lastPublishedAvailabilities = new Map<string | number, boolean>();
2626
/** Mapped by IEEE address */
27-
private readonly pingBackoffs: Map<string, number> = new Map();
27+
private readonly pingBackoffs = new Map<string, number>();
2828
/** IEEE addresses, waiting for last seen changes to take them out of "availability sleep" */
29-
private readonly backoffPausedDevices: Set<string> = new Set();
29+
private readonly backoffPausedDevices = new Set<string>();
3030
/** Mapped by IEEE address */
31-
private readonly retrieveStateDebouncers: Map<string, () => void> = new Map();
31+
private readonly retrieveStateDebouncers = new Map<string, () => void>();
3232
private pingQueue: Device[] = [];
3333
private pingQueueExecuting = false;
3434
private stopped = false;

lib/extension/bind.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ export default class Bind extends Extension {
567567
);
568568

569569
if (polls.length) {
570-
const toPoll: Set<zh.Endpoint> = new Set();
570+
const toPoll = new Set<zh.Endpoint>();
571571

572572
// Add bound devices
573573
for (const endpoint of data.device.zh.endpoints) {

lib/extension/groups.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export default class Groups extends Extension {
106106
// Invalidate the last optimistic group state when group state is changed directly.
107107
delete this.lastOptimisticState[entity.ID];
108108

109-
const groupsToPublish: Set<Group> = new Set();
109+
const groupsToPublish = new Set<Group>();
110110

111111
for (const member of entity.zh.members) {
112112
const device = this.zigbee.resolveEntity(member.getDevice()) as Device;

lib/extension/homeassistant.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1472,7 +1472,7 @@ export class HomeAssistant extends Extension {
14721472
const discovered = this.getDiscovered(entity);
14731473
discovered.discovered = true;
14741474
const lastDiscoveredTopics = Object.keys(discovered.messages);
1475-
const newDiscoveredTopics: Set<string> = new Set();
1475+
const newDiscoveredTopics = new Set<string>();
14761476

14771477
for (const config of this.getConfigs(entity)) {
14781478
const payload = {...config.discovery_payload};

lib/extension/networkMap.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,9 @@ export default class NetworkMap extends Extension {
185185

186186
async networkScan(includeRoutes: boolean): Promise<Zigbee2MQTTNetworkMap> {
187187
logger.info(`Starting network scan (includeRoutes '${includeRoutes}')`);
188-
const lqis: Map<Device, zh.LQI> = new Map();
189-
const routingTables: Map<Device, zh.RoutingTable> = new Map();
190-
const failed: Map<Device, string[]> = new Map();
188+
const lqis = new Map<Device, zh.LQI>();
189+
const routingTables = new Map<Device, zh.RoutingTable>();
190+
const failed = new Map<Device, string[]>();
191191
const requestWithRetry = async <T>(request: () => Promise<T>): Promise<T> => {
192192
try {
193193
const result = await request();

lib/mqtt.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import utils from './util/utils';
1414
const NS = 'z2m:mqtt';
1515

1616
export default class MQTT {
17-
private publishedTopics: Set<string> = new Set();
17+
private publishedTopics = new Set<string>();
1818
private connectionTimer?: NodeJS.Timeout;
1919
private client!: MqttClient;
2020
private eventBus: EventBus;

lib/state.ts

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import fs from 'node:fs';
1+
import {existsSync, readFileSync, writeFileSync} from 'node:fs';
22

33
import objectAssignDeep from 'object-assign-deep';
44

@@ -7,9 +7,8 @@ import logger from './util/logger';
77
import * as settings from './util/settings';
88
import utils from './util/utils';
99

10-
const saveInterval = 1000 * 60 * 5; // 5 minutes
11-
12-
const dontCacheProperties = [
10+
const SAVE_INTERVAL = 1000 * 60 * 5; // 5 minutes
11+
const CACHE_IGNORE_PROPERTIES = [
1312
'action',
1413
'action_.*',
1514
'button',
@@ -32,8 +31,8 @@ const dontCacheProperties = [
3231
];
3332

3433
class State {
35-
private state: {[s: string | number]: KeyValue} = {};
36-
private file = data.joinPath('state.json');
34+
private readonly state = new Map<string | number, KeyValue>();
35+
private readonly file = data.joinPath('state.json');
3736
private timer?: NodeJS.Timeout;
3837

3938
constructor(
@@ -48,26 +47,37 @@ class State {
4847
this.load();
4948

5049
// Save the state on every interval
51-
this.timer = setInterval(() => this.save(), saveInterval);
50+
this.timer = setInterval(() => this.save(), SAVE_INTERVAL);
5251
}
5352

5453
stop(): void {
5554
// Remove any invalid states (ie when the device has left the network) when the system is stopped
56-
for (const key in this.state) {
57-
if (typeof key === 'string' && !this.zigbee.resolveEntity(key)) {
55+
for (const [key] of this.state) {
56+
if (typeof key === 'string' && key.startsWith('0x') && !this.zigbee.resolveEntity(key)) {
5857
// string key = ieeeAddr
59-
delete this.state[key];
58+
this.state.delete(key);
6059
}
6160
}
6261

6362
clearTimeout(this.timer);
6463
this.save();
6564
}
6665

66+
clear(): void {
67+
this.state.clear();
68+
}
69+
6770
private load(): void {
68-
if (fs.existsSync(this.file)) {
71+
this.state.clear();
72+
73+
if (existsSync(this.file)) {
6974
try {
70-
this.state = JSON.parse(fs.readFileSync(this.file, 'utf8'));
75+
const stateObj = JSON.parse(readFileSync(this.file, 'utf8')) as KeyValue;
76+
77+
for (const key in stateObj) {
78+
this.state.set(key.startsWith('0x') ? key : Number.parseInt(key, 10), stateObj[key]);
79+
}
80+
7181
logger.debug(`Loaded state from file ${this.file}`);
7282
} catch (error) {
7383
logger.debug(`Failed to load state from file ${this.file} (corrupt file?) (${(error as Error).message})`);
@@ -80,9 +90,11 @@ class State {
8090
private save(): void {
8191
if (settings.get().advanced.cache_state_persistent) {
8292
logger.debug(`Saving state to file ${this.file}`);
83-
const json = JSON.stringify(this.state, null, 4);
93+
94+
const json = JSON.stringify(Object.fromEntries(this.state), null, 4);
95+
8496
try {
85-
fs.writeFileSync(this.file, json, 'utf8');
97+
writeFileSync(this.file, json, 'utf8');
8698
} catch (error) {
8799
logger.error(`Failed to write state to '${this.file}' (${error})`);
88100
}
@@ -92,28 +104,28 @@ class State {
92104
}
93105

94106
exists(entity: Device | Group): boolean {
95-
return this.state[entity.ID] !== undefined;
107+
return this.state.has(entity.ID);
96108
}
97109

98110
get(entity: Group | Device): KeyValue {
99-
return this.state[entity.ID] || {};
111+
return this.state.get(entity.ID) || {};
100112
}
101113

102114
set(entity: Group | Device, update: KeyValue, reason?: string): KeyValue {
103-
const fromState = this.state[entity.ID] || {};
115+
const fromState = this.state.get(entity.ID) || {};
104116
const toState = objectAssignDeep({}, fromState, update);
105117
const newCache = {...toState};
106118
const entityDontCacheProperties = entity.options.filtered_cache || [];
107119

108-
utils.filterProperties(dontCacheProperties.concat(entityDontCacheProperties), newCache);
120+
utils.filterProperties(CACHE_IGNORE_PROPERTIES.concat(entityDontCacheProperties), newCache);
109121

110-
this.state[entity.ID] = newCache;
122+
this.state.set(entity.ID, newCache);
111123
this.eventBus.emitStateChange({entity, from: fromState, to: toState, reason, update});
112124
return toState;
113125
}
114126

115-
remove(ID: string | number): void {
116-
delete this.state[ID];
127+
remove(ID: string | number): boolean {
128+
return this.state.delete(ID);
117129
}
118130
}
119131

lib/util/settingsMigration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ export function migrateIfNecessary(): void {
510510
while (currentSettings.version !== finalVersion) {
511511
let migrationNotesFileName: string | undefined;
512512
// don't duplicate outputs
513-
const migrationNotes: Set<string> = new Set();
513+
const migrationNotes = new Set<string>();
514514
const transfers: SettingsTransfer[] = [];
515515
const changes: SettingsChange[] = [];
516516
const additions: SettingsAdd[] = [];

lib/zigbee.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export default class Zigbee {
2121
// @ts-expect-error initialized in start
2222
private herdsman: Controller;
2323
private eventBus: EventBus;
24-
private groupLookup: Map<number /* group ID */, Group> = new Map();
25-
private deviceLookup: Map<string /* IEEE address */, Device> = new Map();
24+
private groupLookup = new Map<number /* group ID */, Group>();
25+
private deviceLookup = new Map<string /* IEEE address */, Device>();
2626

2727
constructor(eventBus: EventBus) {
2828
this.eventBus = eventBus;

0 commit comments

Comments
 (0)