diff --git a/src/index.ts b/src/index.ts index 5bd2fad..afd7bb1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,13 +11,8 @@ import { injectFluxInterceptor } from "@lib/api/flux"; import { patchJsx } from "@lib/api/react/jsx"; import { logger } from "@lib/utils/logger"; import { patchSettings } from "@ui/settings"; -import { updaterSettings } from "@lib/api/settings"; import { InteractionManager } from "react-native"; import { getDebugInfo, initDebugger } from "@lib/api/debug"; - -// Debug toggle helper (temporary runtime fallback). The helper is dynamically -// imported when needed (to avoid bundling it permanently) and removed after use. - import * as lib from "./lib"; import { timings } from "@lib/utils/timings"; @@ -66,52 +61,19 @@ export default async () => { const { VdPluginManager } = await import("@core/vendetta/plugins"); const { initPlugins, updatePlugins } = await import("@lib/addons/plugins"); - // Initialize Vendetta plugins (may start many plugins) — do not block UI. - VdPluginManager.initPlugins() - .then((u) => lib.unload.push(u)) - .catch((e) => logger.log("Vendetta init failed:", e)); + await Promise.all([ + VdPluginManager.initPlugins() + .then((u) => u && lib.unload.push(u)) + .catch((e) => logger.log("Vendetta init failed:", e)), + initPlugins() + .catch((e) => logger.log("initPlugins failed:", e)), + ]); - // Start ShiggyCord (Bunny) plugins now without forcing repository updates. - // Plugin repository fetching is deferred so the app can finish launching first. - // Stagger plugin startup to reduce CPU/memory spikes: use smaller batches and a small interval. - // This keeps the UI responsive while plugins initialize in the background. - initPlugins({ staggerInterval: 500, batchSize: 2 }); - - // Attempt a lightweight recovery toggle if some core plugins failed to start. - try { - // Dynamically import the helper (if present) but suppress verbose errors. - const mod = await import("@core/debug/toggleCorePlugins").catch( - () => null, - ); - if (mod?.default) { - // Run helper with minimal noise; ignore failures. - mod.default({ offDuration: 1500 }).catch(() => {}); - } - - // Try to remove the helper source file (best-effort, ignore failures). - await import("@lib/api/native/fs") - .then((fs) => fs.removeFile("src/core/debug/toggleCorePlugins.ts")) - .catch(() => {}); - } catch { - // suppressed - } + updatePlugins() + .catch((e) => logger.log("updatePlugins failed:", e)); // Update fonts in background updateFonts().catch((e) => logger.log("updateFonts failed:", e)); - - // Schedule plugin repository update after a delay (5 minutes) so update work - // does not impact initial launch performance. - setTimeout( - () => { - updatePlugins().catch((e) => - logger.log("updatePlugins failed (deferred 5min):", e), - ); - }, - 5 * 60 * 1000, - ); - - // Note: we intentionally moved the call to `updatePlugins()` above so core - // plugins are registered prior to calling `initPlugins()`. }; // Preferred: wait until interactions finish (animations / navigation). diff --git a/src/lib/addons/plugins/index.ts b/src/lib/addons/plugins/index.ts index 8a9783e..cbb9c31 100644 --- a/src/lib/addons/plugins/index.ts +++ b/src/lib/addons/plugins/index.ts @@ -752,27 +752,54 @@ export async function updatePlugins() { }), ); } + + const enabledIds = [...registeredPlugins.keys()].filter( + (id) => isPluginEnabled(id) && !pluginInstances.has(id), + ); + await Promise.allSettled( + enabledIds.map(async (id) => { + try { + await startPlugin(id).catch((e) => { + console.error(`Failed to start plugin ${id}:`, e); + }); + } catch (error) { + console.error(`Unexpected error while starting plugin ${id}:`, error); + } + }), + ); } /** - * Initialize plugins but stagger startup to avoid CPU spikes during app start. + * Register all installed plugins from local storage manifests without any + * network fetch. This lets initPlugins() start plugins immediately using + * previously-cached scripts, keeping startup snappy. * - * This replaces the original "start all enabled plugins in parallel" approach - * with a configurable staggered startup: plugins are started in small batches and - * we await a small interval between batches. This reduces short-lived CPU and I/O contention - * that causes jank on low-end devices. - * - * Backwards-compatible: callers can still call initPlugins() with no arguments. - * - * Options: - * - staggerInterval (ms) - wait time between batches (default: 100) - * - batchSize - how many plugins to start concurrently in a single batch (default: 3) + * Safe to call multiple times — skips already-registered plugins. */ -export async function initPlugins( - options: { staggerInterval?: number; batchSize?: number } = {}, -) { - const { staggerInterval = 100, batchSize = 3 } = options; +export async function registerInstalledPlugins() { + await awaitStorage(pluginRepositories, pluginSettings); + const ids = Object.keys(pluginSettings ?? {}); + if (ids.length === 0) return; + + // Load all manifest files from local storage in parallel. + const results = await Promise.allSettled( + ids.map(async (id) => { + if (registeredPlugins.has(id) || isCorePlugin(id)) return; + const loaded = await preloadStorageIfExists(`plugins/manifests/${id}.json`); + if (loaded) { + const manifest = getPreloadedStorage( + `plugins/manifests/${id}.json`, + ); + if (manifest) { + registeredPlugins.set(id, manifest); + } + } + }), + ); +} + +export async function initPlugins() { // Ensure core plugins are registered prior to any plugin startup logic so // settings pages and UI that read registered/core plugins show them. // registerCorePlugins is synchronous and idempotent, but call it here as a @@ -784,6 +811,7 @@ export async function initPlugins( } await awaitStorage(pluginRepositories, pluginSettings); + await registerInstalledPlugins(); // Collect enabled plugin ids const enabledIds = [...registeredPlugins.keys()].filter((id) => @@ -792,32 +820,18 @@ export async function initPlugins( if (enabledIds.length === 0) return; - // Helper sleep - const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); - - // Start plugins in batches to avoid starting all at once. - for (let i = 0; i < enabledIds.length; i += batchSize) { - const batch = enabledIds.slice(i, i + batchSize); - - // Start the batch in parallel but don't let exceptions bubble to halt everything. - await Promise.allSettled( - batch.map(async (id) => { - try { - // Only attempt to start if still enabled and not already started - if (!isPluginEnabled(id) || pluginInstances.has(id)) return; - await startPlugin(id).catch((e) => { - // startPlugin may throw; ensure it doesn't crash the whole batch - console.error(`Failed to start plugin ${id}:`, e); - }); - } catch (error) { - console.error(`Unexpected error while starting plugin ${id}:`, error); - } - }), - ); - - // If there are more plugins to start, wait before starting the next batch. - if (i + batchSize < enabledIds.length) { - await sleep(staggerInterval); - } - } + // Start all enabled plugins in parallel + await Promise.allSettled( + enabledIds.map(async (id) => { + try { + // Only attempt to start if still enabled and not already started + if (!isPluginEnabled(id) || pluginInstances.has(id)) return; + await startPlugin(id).catch((e) => { + console.error(`Failed to start plugin ${id}:`, e); + }); + } catch (error) { + console.error(`Unexpected error while starting plugin ${id}:`, error); + } + }), + ); }