From 51bfb1a367c08bf7c2987318386625dedf6763aa Mon Sep 17 00:00:00 2001 From: DJj123dj <80536295+DJj123dj@users.noreply.github.com> Date: Sat, 29 Mar 2025 13:14:59 +0100 Subject: [PATCH] #126 : ODManagers now use Maps instead of Arrays This results in a minor speed increase within large managers (e.g. builders, tickets, blacklist, ...) --- src/core/api/modules/base.ts | 216 ++++++++++++++++++++--------------- 1 file changed, 126 insertions(+), 90 deletions(-) diff --git a/src/core/api/modules/base.ts b/src/core/api/modules/base.ts index 5e5f913..8daa3ae 100644 --- a/src/core/api/modules/base.ts +++ b/src/core/api/modules/base.ts @@ -54,11 +54,27 @@ export type ODDiscordIdType = "role"|"server"|"channel"|"category"|"user"|"membe */ export class ODId { /**The full value of this `ODId` as a `string`. */ - value: string - /**The source of the id (text before `:`). (e.g. `openticket` for all built-in ids) */ + #value: string + /**The full value of this `ODId` as a `string`. */ + set value(id:string){ + this._change(this.#value,id) + this.#value = id + } + get value(){ + return this.#value + } + /**The source of the id (text before `:`). (e.g. `openticket` for all built-in ids) + * + * @deprecated Replaced with `getNamespace()` and will be removed in `v4.1.0`. + */ source: string - /**The identifier of the id (text after `:`). */ + /**The identifier of the id (text after `:`). + * + * @deprecated Replaced with `getIdentifier()` and will be removed in `v4.1.0`. + */ identifier: string + /**The change listener for the parent `ODManager` of this `ODId`. */ + #change: ((oldId:string,newId:string) => void)|null = null constructor(id:ODValidId){ if (typeof id != "string" && !(id instanceof ODId)) throw new ODSystemError("Invalid constructor parameter => id:ODValidId") @@ -74,10 +90,10 @@ export class ODId { } }) - if (result.length > 0) this.value = result.join("") + if (result.length > 0) this.#value = result.join("") else throw new ODSystemError("invalid ID at 'new ODID(id: "+id+")'") - const splitted = this.value.split(":") + const splitted = this.#value.split(":") if (splitted.length > 1){ this.source = splitted[0] splitted.shift() @@ -88,7 +104,7 @@ export class ODId { } }else{ //id is ODId - this.value = id.value + this.#value = id.#value this.source = id.source this.identifier = id.identifier } @@ -96,7 +112,36 @@ export class ODId { /**Returns a string representation of this id. (same as `this.value`) */ toString(){ - return this.value + return this.#value + } + /**The namespace of the id before `:`. (e.g. `openticket` for `openticket:autoclose-enabled`) */ + getNamespace(){ + const splitted = this.#value.split(":") + if (splitted.length > 1) return splitted[0] + else return "" + } + /**The identifier of the id after `:`. (e.g. `autoclose-enabled` for `openticket:autoclose-enabled`) */ + getIdentifier(){ + const splitted = this.#value.split(":") + if (splitted.length > 1){ + splitted.shift() + return splitted.join(":") + }else return this.#value + } + /**Trigger an `onChange()` event in the parent `ODManager` of this class. */ + protected _change(oldId:string,newId:string){ + if (this.#change){ + try{ + this.#change(oldId,newId) + }catch(err){ + process.emit("uncaughtException",err) + throw new ODSystemError("Failed to execute _change() callback!") + } + } + } + /****(❌ SYSTEM ONLY!!)** Set the callback executed when a value inside this class changes. */ + changed(callback:((oldId:string,newId:string) => void)|null){ + this.#change = callback } } @@ -127,6 +172,8 @@ export class ODManagerChangeHelper { } /**## ODManagerRedirectHelper `class` + * @deprecated ### Will be removed in Open Ticket `v4.1.0`! + * * This is Open Ticket ticket manager redirect helper. * * It is used to redirect a source to another source when the id isn't found. @@ -206,8 +253,8 @@ export class ODManager extends ODManagerChangeHe #debug?: ODDebugger /**The message to send when debugging this manager. */ #debugname?: string - /**An array storing all data classes ❌ **(don't edit unless really needed!)***/ - #data: DataType[] = [] + /**The map storing all data classes in this manager. */ + #data: Map = new Map() /**An array storing all listeners when data is added. */ #addListeners: ODManagerAddCallback[] = [] /**An array storing all listeners when data has changed. */ @@ -223,7 +270,7 @@ export class ODManager extends ODManagerChangeHe this.#debugname = debugname } - /**Add data to the manager. The id will be fetched from the data class! You can optionally select to overwrite existing data!*/ + /**Add data to the manager. The `ODId` in the data class will be used as identifier! You can optionally select to overwrite existing data!*/ add(data:DataType|DataType[], overwrite?:boolean): boolean { //repeat same command when data is an array if (Array.isArray(data)){ @@ -233,106 +280,94 @@ export class ODManager extends ODManagerChangeHe return false } + //add listener for data id change => transfer data within manager + data.id.changed((oldId,newId) => { + this.#data.delete(oldId) + this.#data.set(newId,data) + }) + //add data - const existIndex = this.#data.findIndex((d) => d.id.value === data.id.value) - if (existIndex < 0){ - this.#data.push(data) - if (this.#debug) this.#debug.debug("Added new "+this.#debugname+" to manager",[{key:"id",value:data.id.value},{key:"overwrite",value:"false"}]) - - //change listeners - data.changed(() => { - //notify change in upper-manager (because data in this manager changed) - this._change() - this.#changeListeners.forEach((cb) => { - try{ - cb(data) - }catch(err){ - throw new ODSystemError("Failed to run manager onChange() listener.\n"+err) - } - }) - }) - - //add listeners - this.#addListeners.forEach((cb) => { - try{ - cb(data,false) - }catch(err){ - throw new ODSystemError("Failed to run manager onAdd() listener.\n"+err) - } - }) - - //notify change in upper-manager (because data added) - this._change() - - return false - }else{ + let didOverwrite: boolean + if (this.#data.has(data.id.value)){ if (!overwrite) throw new ODSystemError("Id '"+data.id.value+"' already exists in "+this.#debugname+" manager. Use 'overwrite:true' to allow overwriting!") - this.#data[existIndex] = data + this.#data.set(data.id.value,data) + didOverwrite = true if (this.#debug) this.#debug.debug("Added new "+this.#debugname+" to manager",[{key:"id",value:data.id.value},{key:"overwrite",value:"true"}]) - //change listeners - data.changed(() => { - //notify change in upper-manager (because data in this manager changed) - this._change() - this.#changeListeners.forEach((cb) => { - try{ - cb(data) - }catch(err){ - throw new ODSystemError("Failed to run manager onChange() listener.\n"+err) - } - }) - }) + }else{ + this.#data.set(data.id.value,data) + didOverwrite = false + if (this.#debug) this.#debug.debug("Added new "+this.#debugname+" to manager",[{key:"id",value:data.id.value},{key:"overwrite",value:"false"}]) + + } - //add listeners - this.#addListeners.forEach((cb) => { + //emit change listeners + data.changed(() => { + //notify change in upper-manager (because data in this manager changed) + this._change() + this.#changeListeners.forEach((cb) => { try{ - cb(data,true) + cb(data) }catch(err){ - throw new ODSystemError("Failed to run manager onAdd() listener.\n"+err) + throw new ODSystemError("Failed to run manager onChange() listener.\n"+err) } }) + }) - //notify change in upper-manager (because data added) - this._change() - - return true - } + //emit add listeners + this.#addListeners.forEach((cb) => { + try{ + cb(data,didOverwrite) + }catch(err){ + throw new ODSystemError("Failed to run manager onAdd() listener.\n"+err) + } + }) + + //notify change in upper-manager (because data added) + this._change() + + return didOverwrite } /**Get data that matches the `ODId`. Returns the found data.*/ get(id:ODValidId): DataType|null { const newId = new ODId(id) - const d = this.#data.find((a) => a.id.value == newId.value) - if (d) return d + const data = this.#data.get(newId.value) + if (data) return data else{ - const redirect = this.redirects.list().find((redirect) => redirect.fromSource === newId.source) + //DEPRECATED!!! + const redirect = this.redirects.list().find((redirect) => redirect.fromSource === newId.getNamespace()) if (!redirect) return null else{ - const redirectId = new ODId(redirect.toSource+":"+newId.identifier) + const redirectId = new ODId(redirect.toSource+":"+newId.getIdentifier()) return this.get(redirectId) } } } - /**Remove data that matches the `ODId`. Returns the removed data.*/ + /**Remove data that matches the `ODId`. Returns the removed data. */ remove(id:ODValidId): DataType|null { const newId = new ODId(id) - const index = this.#data.findIndex((a) => a.id.value == newId.value) - if (index < 0){ - const redirect = this.redirects.list().find((redirect) => redirect.fromSource === newId.source) + const data = this.#data.get(newId.value) + + if (!data){ + //DEPRECATED!!! + const redirect = this.redirects.list().find((redirect) => redirect.fromSource === newId.getNamespace()) if (!redirect){ if (this.#debug) this.#debug.debug("Removed "+this.#debugname+" from manager",[{key:"id",value:newId.value},{key:"found",value:"false"}]) return null }else{ - const redirectId = new ODId(redirect.toSource+":"+newId.identifier) + const redirectId = new ODId(redirect.toSource+":"+newId.getIdentifier()) return this.remove(redirectId) } + }else{ + this.#data.delete(newId.value) + if (this.#debug) this.#debug.debug("Removed "+this.#debugname+" from manager",[{key:"id",value:newId.value},{key:"found",value:"true"}]) } - if (this.#debug) this.#debug.debug("Removed "+this.#debugname+" from manager",[{key:"id",value:newId.value},{key:"found",value:"true"}]) - const data = this.#data.splice(index,1)[0] - - //change listeners + + //remove all listeners + data.id.changed(null) data.changed(null) - //remove listeners + //emit remove listeners this.#removeListeners.forEach((cb) => { try{ cb(data) @@ -346,39 +381,40 @@ export class ODManager extends ODManagerChangeHe return data } - /**Check if data that matches the `ODId` exists. Returns a boolean.*/ + /**Check if data that matches the `ODId` exists. Returns a boolean. */ exists(id:ODValidId): boolean { const newId = new ODId(id) - const d = this.#data.find((a) => a.id.value == newId.value) - if (d) return true + if (this.#data.has(newId.value)) return true else{ - const redirect = this.redirects.list().find((redirect) => redirect.fromSource === newId.source) + //DEPRECATED!!! + const redirect = this.redirects.list().find((redirect) => redirect.fromSource === newId.getNamespace()) if (!redirect) return false else{ - const redirectId = new ODId(redirect.toSource+":"+newId.identifier) + const redirectId = new ODId(redirect.toSource+":"+newId.getIdentifier()) return this.exists(redirectId) } } } /**Get all data inside this manager*/ getAll(): DataType[] { - return [...this.#data] + return Array.from(this.#data.values()) } /**Get all data that matches inside the filter function*/ getFiltered(predicate:(value:DataType, index:number, array:DataType[]) => unknown): DataType[] { - return this.#data.filter(predicate) + return Array.from(this.#data.values()).filter(predicate) } - /**Get all data that matches the regex*/ + /**Get all data where the `ODId` matches the provided RegExp. */ getRegex(regex:RegExp): DataType[] { - return this.#data.filter((data) => regex.test(data.id.value)) + return Array.from(this.#data.values()).filter((data) => regex.test(data.id.value)) } - /**Get the length of the data inside this manager*/ + /**Get the length/size/amount of the data inside this manager. */ getLength(){ - return this.#data.length + return this.#data.size } /**Get a list of all the ids inside this manager*/ getIds(): ODId[] { - return this.#data.map((d) => d.id) + const ids = Array.from(this.#data.keys()) + return ids.map((id) => new ODId(id)) } /**Run an iterator over all data in this manager. This method also supports async-await behaviour!*/ async loopAll(cb:(data:DataType,id:ODId) => ODPromiseVoid): Promise {