Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Map for runtime Device/Group lookups. #1176

Merged
merged 3 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/controller/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ class Controller extends events.EventEmitter<ControllerEventMap> {

/**
* Get all devices
* @deprecated use getDevicesIterator()
*/
public getDevices(): Device[] {
return Device.all();
Expand Down Expand Up @@ -463,12 +464,13 @@ class Controller extends events.EventEmitter<ControllerEventMap> {
/**
* Get group by ID
*/
public getGroupByID(groupID: number): Group {
public getGroupByID(groupID: number): Group | undefined {
return Group.byGroupID(groupID);
}

/**
* Get all groups
* @deprecated use getGroupsIterator()
*/
public getGroups(): Group[] {
return Group.all();
Expand Down
78 changes: 37 additions & 41 deletions src/controller/model/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class Device extends Entity<ControllerEventMap> {
return this._manufacturerID;
}
get isDeleted(): boolean {
return Boolean(Device.deletedDevices[this.ieeeAddr]);
return Device.deletedDevices.has(this.ieeeAddr);
}
set type(type: DeviceType) {
this._type = type;
Expand Down Expand Up @@ -128,8 +128,12 @@ class Device extends Entity<ControllerEventMap> {
return this._networkAddress;
}
set networkAddress(networkAddress: number) {
Device.nwkToIeeeCache.delete(this._networkAddress);

this._networkAddress = networkAddress;

Device.nwkToIeeeCache.set(this._networkAddress, this.ieeeAddr);

for (const endpoint of this._endpoints) {
endpoint.deviceNetworkAddress = networkAddress;
}
Expand Down Expand Up @@ -198,9 +202,10 @@ class Device extends Entity<ControllerEventMap> {

// This lookup contains all devices that are queried from the database, this is to ensure that always
// the same instance is returned.
private static devices: {[ieeeAddr: string]: Device} = {};
private static readonly devices: Map<string /* IEEE */, Device> = new Map();
private static loadedFromDatabase: boolean = false;
private static deletedDevices: {[ieeeAddr: string]: Device} = {};
private static readonly deletedDevices: Map<string /* IEEE */, Device> = new Map();
private static readonly nwkToIeeeCache: Map<number /* nwk addr */, string /* IEEE */> = new Map();

public static readonly ReportablePropertiesMapping: {
[s: string]: {
Expand Down Expand Up @@ -332,9 +337,10 @@ class Device extends Entity<ControllerEventMap> {
}

public changeIeeeAddress(ieeeAddr: string): void {
delete Device.devices[this.ieeeAddr];
Device.devices.delete(this.ieeeAddr);
this.ieeeAddr = ieeeAddr;
Device.devices[this.ieeeAddr] = this;
Device.devices.set(this.ieeeAddr, this);
Device.nwkToIeeeCache.set(this.networkAddress, this.ieeeAddr);

this.endpoints.forEach((e) => (e.deviceIeeeAddress = ieeeAddr));
this.save();
Expand Down Expand Up @@ -510,9 +516,10 @@ class Device extends Entity<ControllerEventMap> {
* Reset runtime lookups.
*/
public static resetCache(): void {
Device.devices = {};
Device.devices.clear();
Device.loadedFromDatabase = false;
Device.deletedDevices = {};
Device.deletedDevices.clear();
Device.nwkToIeeeCache.clear();
}

private static fromDatabaseEntry(entry: DatabaseEntry): Device {
Expand Down Expand Up @@ -608,7 +615,9 @@ class Device extends Entity<ControllerEventMap> {
if (!Device.loadedFromDatabase) {
for (const entry of Entity.database!.getEntriesIterator(['Coordinator', 'EndDevice', 'Router', 'GreenPower', 'Unknown'])) {
const device = Device.fromDatabaseEntry(entry);
Device.devices[device.ieeeAddr] = device;

Device.devices.set(device.ieeeAddr, device);
Device.nwkToIeeeCache.set(device.networkAddress, device.ieeeAddr);
}

Device.loadedFromDatabase = true;
Expand All @@ -624,31 +633,15 @@ class Device extends Entity<ControllerEventMap> {
public static byIeeeAddr(ieeeAddr: string, includeDeleted: boolean = false): Device | undefined {
Device.loadFromDatabaseIfNecessary();

return includeDeleted ? (Device.deletedDevices[ieeeAddr] ?? Device.devices[ieeeAddr]) : Device.devices[ieeeAddr];
return includeDeleted ? (Device.deletedDevices.get(ieeeAddr) ?? Device.devices.get(ieeeAddr)) : Device.devices.get(ieeeAddr);
}

public static byNetworkAddress(networkAddress: number, includeDeleted: boolean = false): Device | undefined {
Device.loadFromDatabaseIfNecessary();

if (includeDeleted) {
for (const ieeeAddress in Device.deletedDevices) {
const device = Device.deletedDevices[ieeeAddress];
const ieeeAddr = Device.nwkToIeeeCache.get(networkAddress);

/* istanbul ignore else */
if (device.networkAddress === networkAddress) {
return device;
}
}
}

for (const ieeeAddress in Device.devices) {
const device = Device.devices[ieeeAddress];

/* istanbul ignore else */
if (device.networkAddress === networkAddress) {
return device;
}
}
return ieeeAddr ? Device.byIeeeAddr(ieeeAddr, includeDeleted) : undefined;
}

public static byType(type: DeviceType): Device[] {
Expand All @@ -661,32 +654,34 @@ class Device extends Entity<ControllerEventMap> {
return devices;
}

/**
* @deprecated use allIterator()
*/
public static all(): Device[] {
Device.loadFromDatabaseIfNecessary();
return Object.values(Device.devices);
return Array.from(Device.devices.values());
}

public static *allIterator(predicate?: (value: Device) => boolean): Generator<Device> {
Device.loadFromDatabaseIfNecessary();

for (const ieeeAddr in Device.devices) {
const device = Device.devices[ieeeAddr];

for (const device of Device.devices.values()) {
if (!predicate || predicate(device)) {
yield device;
}
}
}

public undelete(interviewCompleted?: boolean): void {
assert(Device.deletedDevices[this.ieeeAddr], `Device '${this.ieeeAddr}' is not deleted`);

Device.devices[this.ieeeAddr] = this;
delete Device.deletedDevices[this.ieeeAddr];
if (Device.deletedDevices.delete(this.ieeeAddr)) {
Device.devices.set(this.ieeeAddr, this);

this._interviewCompleted = interviewCompleted ?? this._interviewCompleted;
this._interviewCompleted = interviewCompleted ?? this._interviewCompleted;

Entity.database!.insert(this.toDatabaseEntry());
Entity.database!.insert(this.toDatabaseEntry());
} else {
throw new Error(`Device '${this.ieeeAddr}' is not deleted`);
}
}

public static create(
Expand All @@ -708,7 +703,7 @@ class Device extends Entity<ControllerEventMap> {
): Device {
Device.loadFromDatabaseIfNecessary();

if (Device.devices[ieeeAddr]) {
if (Device.devices.has(ieeeAddr)) {
throw new Error(`Device with IEEE address '${ieeeAddr}' already exists`);
}

Expand Down Expand Up @@ -741,7 +736,8 @@ class Device extends Entity<ControllerEventMap> {
);

Entity.database!.insert(device.toDatabaseEntry());
Device.devices[device.ieeeAddr] = device;
Device.devices.set(device.ieeeAddr, device);
Device.nwkToIeeeCache.set(device.networkAddress, device.ieeeAddr);
return device;
}

Expand Down Expand Up @@ -1084,8 +1080,8 @@ class Device extends Entity<ControllerEventMap> {
Entity.database!.remove(this.ID);
}

Device.deletedDevices[this.ieeeAddr] = this;
delete Device.devices[this.ieeeAddr];
Device.deletedDevices.set(this.ieeeAddr, this);
Device.devices.delete(this.ieeeAddr);

// Clear all data in case device joins again
this._interviewCompleted = false;
Expand Down
25 changes: 13 additions & 12 deletions src/controller/model/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Group extends Entity {

// This lookup contains all groups that are queried from the database, this is to ensure that always
// the same instance is returned.
private static groups: {[groupID: number]: Group} = {};
private static readonly groups: Map<number /* groupID */, Group> = new Map();
private static loadedFromDatabase: boolean = false;

private constructor(databaseID: number, groupID: number, members: Set<Endpoint>, meta: KeyValue) {
Expand All @@ -54,7 +54,7 @@ class Group extends Entity {
* Reset runtime lookups.
*/
public static resetCache(): void {
Group.groups = {};
Group.groups.clear();
Group.loadedFromDatabase = false;
}

Expand Down Expand Up @@ -91,29 +91,30 @@ class Group extends Entity {
if (!Group.loadedFromDatabase) {
for (const entry of Entity.database!.getEntriesIterator(['Group'])) {
const group = Group.fromDatabaseEntry(entry);
Group.groups[group.groupID] = group;
Group.groups.set(group.groupID, group);
}

Group.loadedFromDatabase = true;
}
}

public static byGroupID(groupID: number): Group {
public static byGroupID(groupID: number): Group | undefined {
Group.loadFromDatabaseIfNecessary();
return Group.groups[groupID];
return Group.groups.get(groupID);
}

/**
* @deprecated use allIterator()
*/
public static all(): Group[] {
Group.loadFromDatabaseIfNecessary();
return Object.values(Group.groups);
return Array.from(Group.groups.values());
}

public static *allIterator(predicate?: (value: Group) => boolean): Generator<Group> {
Group.loadFromDatabaseIfNecessary();

for (const ieeeAddr in Group.groups) {
const group = Group.groups[ieeeAddr];

for (const group of Group.groups.values()) {
/* istanbul ignore else */
if (!predicate || predicate(group)) {
yield group;
Expand All @@ -129,15 +130,15 @@ class Group extends Entity {

Group.loadFromDatabaseIfNecessary();

if (Group.groups[groupID]) {
if (Group.groups.has(groupID)) {
throw new Error(`Group with groupID '${groupID}' already exists`);
}

const databaseID = Entity.database!.newID();
const group = new Group(databaseID, groupID, new Set(), {});
Entity.database!.insert(group.toDatabaseRecord());

Group.groups[group.groupID] = group;
Group.groups.set(group.groupID, group);
return group;
}

Expand All @@ -156,7 +157,7 @@ class Group extends Entity {
Entity.database!.remove(this.databaseID);
}

delete Group.groups[this.groupID];
Group.groups.delete(this.groupID);
}

public save(writeDatabase = true): void {
Expand Down
Loading