From e725344bfc23960a7b7bb51fe83af3793b9a107b Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 12 May 2026 06:19:57 -0600 Subject: [PATCH] perf: cache static assets and loader traversal --- .../src/commands/duels/duels.command.tsx | 27 ++++++++++++++----- .../leaderboards/base.leaderboard-command.tsx | 4 +-- packages/assets/src/index.ts | 19 +++++++++++-- .../discord/src/command/command.loader.ts | 9 +++---- packages/discord/src/event/event.loader.ts | 9 +++---- 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/apps/discord-bot/src/commands/duels/duels.command.tsx b/apps/discord-bot/src/commands/duels/duels.command.tsx index c18183e31..9203bcaf4 100644 --- a/apps/discord-bot/src/commands/duels/duels.command.tsx +++ b/apps/discord-bot/src/commands/duels/duels.command.tsx @@ -26,6 +26,26 @@ interface PreProfileData { modeIcons: DuelsModeIcons; } +let modeIcons: Promise | undefined; + +const getModeIcons = async () => { + if (modeIcons) return modeIcons; + + modeIcons = readdir(getAssetPath("duels")) + .then((modeIconPaths) => + Promise.all( + modeIconPaths.map(async (mode) => [mode.replace(".png", ""), await loadImage(getAssetPath(`duels/${mode}`))]) + ) + ) + .then((icons) => Object.fromEntries(icons)) + .catch((error) => { + modeIcons = undefined; + throw error; + }); + + return modeIcons; +}; + @Command({ description: (t) => t("commands.duels") }) export class DuelsCommand extends BaseHypixelCommand { public constructor() { @@ -33,12 +53,7 @@ export class DuelsCommand extends BaseHypixelCommand } public async getPreProfileData(): Promise { - const modeIconPaths = await readdir(getAssetPath("duels")); - const modeIcons = await Promise.all( - modeIconPaths.map(async (mode) => [mode.replace(".png", ""), await loadImage(getAssetPath(`duels/${mode}`))]) - ); - - return { modeIcons: Object.fromEntries(modeIcons) }; + return { modeIcons: await getModeIcons() }; } public getModeEmojis(modes: GameModeWithSubModes[]): ModeEmoji[] { diff --git a/apps/discord-bot/src/commands/leaderboards/base.leaderboard-command.tsx b/apps/discord-bot/src/commands/leaderboards/base.leaderboard-command.tsx index 7c32580d2..a2d2e1868 100644 --- a/apps/discord-bot/src/commands/leaderboards/base.leaderboard-command.tsx +++ b/apps/discord-bot/src/commands/leaderboards/base.leaderboard-command.tsx @@ -241,7 +241,7 @@ export class BaseLeaderboardCommand { cache.clear(); }, 300_000); - currentPage = page || currentPage; + currentPage = page ?? currentPage; return { ...message, components: [row] }; } @@ -271,7 +271,7 @@ export class BaseLeaderboardCommand { getLeaderboardDataIcon ); - if (params.type === LeaderboardQuery.PAGE && page) cache.set(page, message); + if (params.type === LeaderboardQuery.PAGE && page !== null) cache.set(page, message); return [message, page]; } diff --git a/packages/assets/src/index.ts b/packages/assets/src/index.ts index dae04fd37..266c7040d 100644 --- a/packages/assets/src/index.ts +++ b/packages/assets/src/index.ts @@ -21,7 +21,22 @@ const checkAsset = (file: string) => export const getAssetPath = (path: string) => join(PATH, checkAsset(path), path); -const getImage = (path: string) => loadImage(getAssetPath(path)); +const images = new Map>(); + +const getCachedImage = (path: string) => { + const cachedImage = images.get(path); + if (cachedImage) return cachedImage; + + const image = loadImage(path).catch((error) => { + images.delete(path); + throw error; + }); + + images.set(path, image); + return image; +}; + +const getImage = (path: string) => getCachedImage(getAssetPath(path)); /** * @@ -73,7 +88,7 @@ export function getLogo( userOrLogoOrPath: User | UserLogo | string | null, size?: number ): Promise { - return loadImage(getLogoPath(userOrLogoOrPath as User, size)); + return getCachedImage(getLogoPath(userOrLogoOrPath as User, size)); } export function getLogoPath( diff --git a/packages/discord/src/command/command.loader.ts b/packages/discord/src/command/command.loader.ts index e644107df..b41971c44 100644 --- a/packages/discord/src/command/command.loader.ts +++ b/packages/discord/src/command/command.loader.ts @@ -10,7 +10,6 @@ import { CommandBuilder } from "./command.builder.js"; import { Container } from "typedi"; import { Logger } from "@statsify/logger"; import { readdir } from "node:fs/promises"; -import { statSync } from "node:fs"; import type { CommandResolvable } from "./command.resolvable.js"; export class CommandLoader { @@ -58,15 +57,15 @@ export class CommandLoader { private static async getCommandFiles(dir: string): Promise { const toLoad: string[] = []; - const files = await readdir(dir); + const files = await readdir(dir, { withFileTypes: true }); await Promise.all( files.map(async (file) => { - const path = `${dir}/${file}`; + const path = `${dir}/${file.name}`; - if (statSync(path).isDirectory()) { + if (file.isDirectory()) { toLoad.push(...(await this.getCommandFiles(path))); - } else if (file.endsWith(".command.js")) { + } else if (file.name.endsWith(".command.js")) { toLoad.push(path); } }) diff --git a/packages/discord/src/event/event.loader.ts b/packages/discord/src/event/event.loader.ts index 830bf1752..ceb54aadc 100644 --- a/packages/discord/src/event/event.loader.ts +++ b/packages/discord/src/event/event.loader.ts @@ -12,7 +12,6 @@ import { GatewayDispatchEvents } from "discord-api-types/v10"; import { Logger } from "@statsify/logger"; import { WebsocketShard } from "tiny-discord"; import { readdir } from "node:fs/promises"; -import { statSync } from "node:fs"; export class EventLoader { private static readonly logger = new Logger("EventLoader"); @@ -54,15 +53,15 @@ export class EventLoader { private static async getEventFiles(dir: string): Promise { const toLoad: string[] = []; - const files = await readdir(dir); + const files = await readdir(dir, { withFileTypes: true }); await Promise.all( files.map(async (file) => { - const path = `${dir}/${file}`; + const path = `${dir}/${file.name}`; - if (statSync(path).isDirectory()) { + if (file.isDirectory()) { toLoad.push(...(await this.getEventFiles(path))); - } else if (file.endsWith(".event.js")) { + } else if (file.name.endsWith(".event.js")) { toLoad.push(path); } })