From e87dff66ac0e3702a5d65eb7049e69422648f04d Mon Sep 17 00:00:00 2001 From: Androz2091 Date: Tue, 26 May 2020 16:00:31 +0200 Subject: [PATCH] Replace Map with Collection --- structures/guild.ts | 11 +- utils/cache.ts | 17 ++-- utils/collection.ts | 239 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 254 insertions(+), 13 deletions(-) create mode 100644 utils/collection.ts diff --git a/structures/guild.ts b/structures/guild.ts index dd0c95287..45df63a94 100644 --- a/structures/guild.ts +++ b/structures/guild.ts @@ -1,3 +1,4 @@ +import Collection from "../utils/collection.ts"; import { identifyPayload } from "../module/client.ts"; import { endpoints } from "../constants/discord.ts"; import { formatImageURL } from "../utils/cdn.ts"; @@ -66,21 +67,21 @@ export const createGuild = (data: CreateGuildPayload, shardID: number) => { preferredLocale: data.preferred_locale, /** The roles in the guild */ - roles: new Map(data.roles.map((r) => [r.id, createRole(r)])), + roles: new Collection(data.roles.map((r) => [r.id, createRole(r)])), /** When this guild was joined at. */ joinedAt: Date.parse(data.joined_at), /** The users in this guild. */ - members: new Map(), + members: new Collection(), /** The channels in the guild */ - channels: new Map( + channels: new Collection( data.channels.map((c) => [c.id, createChannel(c, data.id)]), ), /** The presences of all the users in the guild. */ - presences: new Map(data.presences.map((p) => [p.user.id, p])), + presences: new Collection(data.presences.map((p) => [p.user.id, p])), /** The total number of members in this guild. This value is updated as members leave and join the server. However, if you do not have the intent enabled to be able to listen to these events, then this will not be accurate. */ memberCount: data.member_count || 0, /** The Voice State data for each user in a voice channel in this server. */ - voiceStates: new Map(data.voice_states.map((vs) => [vs.user_id, { + voiceStates: new Collection(data.voice_states.map((vs) => [vs.user_id, { ...vs, guildID: vs.guild_id, channelID: vs.channel_id, diff --git a/utils/cache.ts b/utils/cache.ts index c2d576b79..ab01cfe63 100644 --- a/utils/cache.ts +++ b/utils/cache.ts @@ -1,17 +1,18 @@ +import Collection from "./collection.ts"; import { Message } from "../structures/message.ts"; import { Guild } from "../structures/guild.ts"; import { Channel } from "../structures/channel.ts"; export interface CacheData { - guilds: Map; - channels: Map; - messages: Map; - unavailableGuilds: Map; + guilds: Collection; + channels: Collection; + messages: Collection; + unavailableGuilds: Collection; } export const cache: CacheData = { - guilds: new Map(), - channels: new Map(), - messages: new Map(), - unavailableGuilds: new Map(), + guilds: new Collection(), + channels: new Collection(), + messages: new Collection(), + unavailableGuilds: new Collection(), }; diff --git a/utils/collection.ts b/utils/collection.ts new file mode 100644 index 000000000..e23a107cf --- /dev/null +++ b/utils/collection.ts @@ -0,0 +1,239 @@ +class Collection extends Map { + private _array!: V[] | null; + public static readonly default: typeof Collection = Collection; + public ["constructor"]: typeof Collection; + + public constructor(entries?: ReadonlyArray | null) { + super(entries); + + Object.defineProperty( + this, + "_array", + { value: null, writable: true, configurable: true }, + ); + + Object.defineProperty( + this, + "_keyArray", + { value: null, writable: true, configurable: true }, + ); + } + + public get(key: K): V | undefined { + return super.get(key); + } + + public set(key: K, value: V): this { + this._array = null; + return super.set(key, value); + } + + public has(key: K): boolean { + return super.has(key); + } + + public delete(key: K): boolean { + this._array = null; + return super.delete(key); + } + + public clear(): void { + return super.clear(); + } + + public array(): V[] { + if (!this._array || this._array.length !== this.size) { + this._array = [...this.values()]; + } + return this._array; + } + + public first(): V | undefined; + public first(amount: number): V[]; + public first(amount?: number): V | V[] | undefined { + if (typeof amount === "undefined") return this.values().next().value; + if (amount < 0) return this.last(amount * -1); + amount = Math.min(this.size, amount); + const iter = this.values(); + return Array.from({ length: amount }, (): V => iter.next().value); + } + + public last(): V | undefined; + public last(amount: number): V[]; + public last(amount?: number): V | V[] | undefined { + const arr = this.array(); + if (typeof amount === "undefined") return arr[arr.length - 1]; + if (amount < 0) return this.first(amount * -1); + if (!amount) return []; + return arr.slice(-amount); + } + + public random(): V; + public random(amount: number): V[]; + public random(amount?: number): V | V[] { + let arr = this.array(); + if ( + typeof amount === "undefined" + ) { + return arr[Math.floor(Math.random() * arr.length)]; + } + if (arr.length === 0 || !amount) return []; + arr = arr.slice(); + return Array.from( + { length: amount }, + (): V => arr.splice(Math.floor(Math.random() * arr.length), 1)[0], + ); + } + + public find( + fn: (value: V, key: K, collection: this) => boolean, + ): V | undefined; + public find( + fn: (this: T, value: V, key: K, collection: this) => boolean, + thisArg: T, + ): V | undefined; + public find( + fn: (value: V, key: K, collection: this) => boolean, + thisArg?: unknown, + ): V | undefined { + if (typeof thisArg !== "undefined") fn = fn.bind(thisArg); + for (const [key, val] of this) { + if (fn(val, key, this)) return val; + } + return undefined; + } + + public filter(fn: (value: V, key: K, collection: this) => boolean): this; + public filter( + fn: (this: T, value: V, key: K, collection: this) => boolean, + thisArg: T, + ): this; + public filter( + fn: (value: V, key: K, collection: this) => boolean, + thisArg?: unknown, + ): this { + if (typeof thisArg !== "undefined") fn = fn.bind(thisArg); + const results = new this.constructor[Symbol.species]() as this; + for (const [key, val] of this) { + if (fn(val, key, this)) results.set(key, val); + } + return results; + } + + public map(fn: (value: V, key: K, collection: this) => T): T[]; + public map( + fn: (this: This, value: V, key: K, collection: this) => T, + thisArg: This, + ): T[]; + public map( + fn: (value: V, key: K, collection: this) => T, + thisArg?: unknown, + ): T[] { + if (typeof thisArg !== "undefined") fn = fn.bind(thisArg); + const iter = this.entries(); + return Array.from({ length: this.size }, (): T => { + const [key, value] = iter.next().value; + return fn(value, key, this); + }); + } + + public some(fn: (value: V, key: K, collection: this) => boolean): boolean; + public some( + fn: (this: T, value: V, key: K, collection: this) => boolean, + thisArg: T, + ): boolean; + public some( + fn: (value: V, key: K, collection: this) => boolean, + thisArg?: unknown, + ): boolean { + if (typeof thisArg !== "undefined") fn = fn.bind(thisArg); + for (const [key, val] of this) { + if (fn(val, key, this)) return true; + } + return false; + } + + public every(fn: (value: V, key: K, collection: this) => boolean): boolean; + public every( + fn: (this: T, value: V, key: K, collection: this) => boolean, + thisArg: T, + ): boolean; + public every( + fn: (value: V, key: K, collection: this) => boolean, + thisArg?: unknown, + ): boolean { + if (typeof thisArg !== "undefined") fn = fn.bind(thisArg); + for (const [key, val] of this) { + if (!fn(val, key, this)) return false; + } + return true; + } + + public reduce( + fn: (accumulator: T, value: V, key: K, collection: this) => T, + initialValue?: T, + ): T { + let accumulator!: T; + + if (typeof initialValue !== "undefined") { + accumulator = initialValue; + for (const [key, val] of this) { + accumulator = fn(accumulator, val, key, this); + } + return accumulator; + } + let first = true; + for (const [key, val] of this) { + if (first) { + accumulator = val as unknown as T; + first = false; + continue; + } + accumulator = fn(accumulator, val, key, this); + } + + // No items iterated. + if (first) { + throw new TypeError("Reduce of empty collection with no initial value"); + } + + return accumulator; + } + + public each(fn: (value: V, key: K, collection: this) => void): this; + public each( + fn: (this: T, value: V, key: K, collection: this) => void, + thisArg: T, + ): this; + public each( + fn: (value: V, key: K, collection: this) => void, + thisArg?: unknown, + ): this { + this.forEach(fn as (value: V, key: K, map: Map) => void, thisArg); + return this; + } + + public sort( + compareFunction: ( + firstValue: V, + secondValue: V, + firstKey: K, + secondKey: K, + ) => number = (x, y): number => Number(x > y) || Number(x === y) - 1, + ): this { + const entries = [...this.entries()]; + entries.sort((a, b): number => compareFunction(a[1], b[1], a[0], b[0])); + + // Perform clean-up + super.clear(); + this._array = null; + + // Set the new entries + for (const [k, v] of entries) { + super.set(k, v); + } + return this; + } +} + +export default Collection;