Skip to content

Commit

Permalink
fix: Improve loops performance (#1130)
Browse files Browse the repository at this point in the history
* Improve loops performance

* Fix prettier.

* Reset runtime lookups on controller stop (supersedes #1131).
  • Loading branch information
Nerivec committed Jul 25, 2024
1 parent c4e7395 commit b2e9778
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 105 deletions.
1 change: 0 additions & 1 deletion src/adapter/ember/adapter/emberAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2389,7 +2389,6 @@ export class EmberAdapter extends Adapter {
destinationAddressOrGroup as number, // not used with UNICAST_BINDING
destinationEndpoint, // not used with MULTICAST_BINDING
);
console.log(zdoPayload);
const [status, apsFrame] = await this.sendZDORequest(
destinationNetworkAddress,
Zdo.ClusterId.BIND_REQUEST,
Expand Down
49 changes: 42 additions & 7 deletions src/controller/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,11 @@ class Controller extends events.EventEmitter {
}

logger.debug('Clearing database...', NS);
for (const group of Group.all()) {
for (const group of Group.allIterator()) {
group.removeFromDatabase();
}

for (const device of Device.all()) {
for (const device of Device.allIterator()) {
device.removeFromDatabase();
}
}
Expand Down Expand Up @@ -334,14 +334,17 @@ class Controller extends events.EventEmitter {

this.adapterDisconnected = true;
}

Device.resetCache();
Group.resetCache();
}

private databaseSave(): void {
for (const device of Device.all()) {
for (const device of Device.allIterator()) {
device.save(false);
}

for (const group of Group.all()) {
for (const group of Group.allIterator()) {
group.save(false);
}

Expand All @@ -352,7 +355,7 @@ class Controller extends events.EventEmitter {
this.databaseSave();
if (this.options.backupPath && (await this.adapter.supportsBackup())) {
logger.debug('Creating coordinator backup', NS);
const backup = await this.adapter.backup(Device.all().map((d) => d.ieeeAddr));
const backup = await this.adapter.backup(this.getDeviceIeeeAddresses());
const unifiedBackup = await BackupUtils.toUnifiedBackup(backup);
const tmpBackupPath = this.options.backupPath + '.tmp';
fs.writeFileSync(tmpBackupPath, JSON.stringify(unifiedBackup, null, 2));
Expand All @@ -363,9 +366,14 @@ class Controller extends events.EventEmitter {

public async coordinatorCheck(): Promise<{missingRouters: Device[]}> {
if (await this.adapter.supportsBackup()) {
const backup = await this.adapter.backup(Device.all().map((d) => d.ieeeAddr));
const backup = await this.adapter.backup(this.getDeviceIeeeAddresses());
const devicesInBackup = backup.devices.map((d) => `0x${d.ieeeAddress.toString('hex')}`);
const missingRouters = this.getDevices().filter((d) => d.type === 'Router' && !devicesInBackup.includes(d.ieeeAddr));
const missingRouters = [];

for (const device of this.getDevicesIterator((d) => d.type === 'Router' && !devicesInBackup.includes(d.ieeeAddr))) {
missingRouters.push(device);
}

return {missingRouters};
} else {
throw new Error("Coordinator does not coordinator check because it doesn't support backups");
Expand Down Expand Up @@ -396,6 +404,13 @@ class Controller extends events.EventEmitter {
return Device.all();
}

/**
* Get iterator for all devices
*/
public getDevicesIterator(predicate?: (value: Device) => boolean): Generator<Device> {
return Device.allIterator(predicate);
}

/**
* Get all devices with a specific type
*/
Expand All @@ -417,6 +432,19 @@ class Controller extends events.EventEmitter {
return Device.byNetworkAddress(networkAddress);
}

/**
* Get IEEE address for all devices
*/
public getDeviceIeeeAddresses(): string[] {
const deviceIeeeAddresses = [];

for (const device of Device.allIterator()) {
deviceIeeeAddresses.push(device.ieeeAddr);
}

return deviceIeeeAddresses;
}

/**
* Get group by ID
*/
Expand All @@ -431,6 +459,13 @@ class Controller extends events.EventEmitter {
return Group.all();
}

/**
* Get iterator for all groups
*/
public getGroupsIterator(predicate?: (value: Group) => boolean): Generator<Group> {
return Group.allIterator(predicate);
}

/**
* Create a Group
*/
Expand Down
74 changes: 44 additions & 30 deletions src/controller/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,58 +20,70 @@ class Database {
const entries: {[id: number]: DatabaseEntry} = {};

if (fs.existsSync(path)) {
const rows = fs
.readFileSync(path, 'utf-8')
.split('\n')
.map((r) => r.trim())
.filter((r) => r != '');
for (const row of rows) {
const json = JSON.parse(row);
if (json.hasOwnProperty('id')) {
entries[json.id] = json;
const file = fs.readFileSync(path, 'utf-8');

for (const row of file.split('\n')) {
if (!row) {
continue;
}

try {
const json = JSON.parse(row);

if (json.id != undefined) {
entries[json.id] = json;
}
} catch (error) {
logger.error(`Corrupted database line, ignoring. ${error}`, NS);
}
}
}

return new Database(entries, path);
}

public getEntries(type: EntityType[]): DatabaseEntry[] {
return Object.values(this.entries).filter((e) => type.includes(e.type));
public *getEntriesIterator(type: EntityType[]): Generator<DatabaseEntry> {
for (const id in this.entries) {
const entry = this.entries[id];

if (type.includes(entry.type)) {
yield entry;
}
}
}

public insert(DatabaseEntry: DatabaseEntry): void {
if (this.entries[DatabaseEntry.id]) {
throw new Error(`DatabaseEntry with ID '${DatabaseEntry.id}' already exists`);
public insert(databaseEntry: DatabaseEntry): void {
if (this.entries[databaseEntry.id]) {
throw new Error(`DatabaseEntry with ID '${databaseEntry.id}' already exists`);
}

this.entries[DatabaseEntry.id] = DatabaseEntry;
this.entries[databaseEntry.id] = databaseEntry;
this.write();
}

public update(DatabaseEntry: DatabaseEntry, write: boolean): void {
if (!this.entries[DatabaseEntry.id]) {
throw new Error(`DatabaseEntry with ID '${DatabaseEntry.id}' does not exist`);
public update(databaseEntry: DatabaseEntry, write: boolean): void {
if (!this.entries[databaseEntry.id]) {
throw new Error(`DatabaseEntry with ID '${databaseEntry.id}' does not exist`);
}

this.entries[DatabaseEntry.id] = DatabaseEntry;
this.entries[databaseEntry.id] = databaseEntry;

if (write) {
this.write();
}
}

public remove(ID: number): void {
if (!this.entries[ID]) {
throw new Error(`DatabaseEntry with ID '${ID}' does not exist`);
public remove(id: number): void {
if (!this.entries[id]) {
throw new Error(`DatabaseEntry with ID '${id}' does not exist`);
}

delete this.entries[ID];
delete this.entries[id];
this.write();
}

public has(ID: number): boolean {
return this.entries.hasOwnProperty(ID);
public has(id: number): boolean {
return Boolean(this.entries[id]);
}

public newID(): number {
Expand All @@ -81,13 +93,15 @@ class Database {

public write(): void {
logger.debug(`Writing database to '${this.path}'`, NS);
const lines = [];
for (const DatabaseEntry of Object.values(this.entries)) {
const json = JSON.stringify(DatabaseEntry);
lines.push(json);
let lines = '';

for (const id in this.entries) {
lines += JSON.stringify(this.entries[id]) + `\n`;
}

const tmpPath = this.path + '.tmp';
fs.writeFileSync(tmpPath, lines.join('\n'));

fs.writeFileSync(tmpPath, lines.slice(0, -1)); // remove last newline, no effect if empty string
// Ensure file is on disk https://github.com/Koenkk/zigbee2mqtt/issues/11759
const fd = fs.openSync(tmpPath, 'r+');
fs.fsyncSync(fd);
Expand Down
Loading

0 comments on commit b2e9778

Please sign in to comment.