Vencord/src/utils/lazy.ts

112 lines
4 KiB
TypeScript
Raw Normal View History

2022-10-21 23:17:06 +00:00
/*
2024-05-03 02:18:12 +00:00
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
2022-10-21 23:17:06 +00:00
export function makeLazy<T>(factory: () => T, attempts = 5, { isIndirect = false }: { isIndirect?: boolean; } = {}): () => T {
2023-09-23 01:25:19 +00:00
let tries = 0;
2023-05-05 23:36:00 +00:00
let cache: T;
2024-05-04 23:56:32 +00:00
const getter = () => {
if (!cache && attempts > tries) {
2023-09-23 01:25:19 +00:00
cache = factory();
if (!cache && attempts === ++tries && !isIndirect) {
2024-05-04 23:56:32 +00:00
console.error(`Lazy factory failed:\n\n${factory}`);
2024-05-03 02:18:12 +00:00
}
2023-09-23 01:25:19 +00:00
}
2023-09-23 01:25:19 +00:00
return cache;
};
2024-05-04 23:56:32 +00:00
getter.$$vencordLazyFailed = () => tries === attempts;
2024-05-04 23:56:32 +00:00
return getter;
2023-05-05 23:36:00 +00:00
}
2022-11-06 17:00:59 +00:00
// Proxies demand that these properties be unmodified, so proxyLazy
// will always return the function default for them.
const unconfigurable = ["arguments", "caller", "prototype"];
2022-10-14 19:34:35 +00:00
2024-05-03 02:18:12 +00:00
const handler: ProxyHandler<any> = {
...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName =>
[propName, (target: any, ...args: any[]) => Reflect[propName](target[proxyLazyGet](), ...args)]
)),
ownKeys: target => {
const keys = Reflect.ownKeys(target[proxyLazyGet]());
for (const key of unconfigurable) {
if (!keys.includes(key)) keys.push(key);
}
return keys;
},
getOwnPropertyDescriptor: (target, p) => {
if (typeof p === "string" && unconfigurable.includes(p))
return Reflect.getOwnPropertyDescriptor(target, p);
2022-11-06 17:00:59 +00:00
2024-05-03 02:18:12 +00:00
const descriptor = Reflect.getOwnPropertyDescriptor(target[proxyLazyGet](), p);
if (descriptor) Object.defineProperty(target, p, descriptor);
return descriptor;
2022-11-06 17:00:59 +00:00
}
};
export const proxyLazyGet = Symbol.for("vencord.lazy.get");
export const proxyLazyCache = Symbol.for("vencord.lazy.cached");
2022-10-14 20:38:36 +00:00
2022-10-14 19:34:35 +00:00
/**
2024-05-03 02:18:12 +00:00
* Wraps the result of factory in a Proxy you can consume as if it wasn't lazy.
* On first property access, the factory is evaluated
* @param factory Factory returning the result
* @param attempts How many times to try to evaluate the factory before giving up
* @returns Result of factory function
2022-10-14 19:34:35 +00:00
*/
2024-05-03 02:18:12 +00:00
export function proxyLazy<T = any>(factory: () => T, attempts = 5, isChild = false): T {
const get = makeLazy(factory, attempts, { isIndirect: true }) as any;
2024-05-03 02:18:12 +00:00
let isSameTick = true;
2024-05-03 02:18:12 +00:00
if (!isChild) setTimeout(() => isSameTick = false, 0);
2023-07-05 22:53:43 +00:00
const proxyDummy = Object.assign(function () { }, {
2024-05-03 02:18:12 +00:00
[proxyLazyGet]() {
2024-05-04 23:56:32 +00:00
if (!proxyDummy[proxyLazyCache]) {
if (!get.$$vencordLazyFailed()) {
proxyDummy[proxyLazyCache] = get();
}
2024-05-03 02:18:12 +00:00
if (!proxyDummy[proxyLazyCache]) {
2024-05-04 23:56:32 +00:00
throw new Error(`proxyLazy factory failed:\n\n${factory}`);
2024-05-03 02:18:12 +00:00
}
2023-07-05 22:53:43 +00:00
}
2024-05-03 02:18:12 +00:00
return proxyDummy[proxyLazyCache];
},
[proxyLazyCache]: void 0 as T | undefined
});
2022-11-06 17:00:59 +00:00
return new Proxy(proxyDummy, {
...handler,
get(target, p, receiver) {
if (p === proxyLazyGet) return target[proxyLazyGet];
if (p === proxyLazyCache) return target[proxyLazyCache];
2024-05-03 02:18:12 +00:00
// If we're still in the same tick, it means the lazy was immediately used.
// thus, we lazy proxy the get access to make things like destructuring work as expected
// meow here will also be a lazy
// `const { meow } = findByPropsLazy("meow");`
2024-05-03 02:18:12 +00:00
if (!isChild && isSameTick) {
return proxyLazy(
2024-05-03 02:18:12 +00:00
() => Reflect.get(target[proxyLazyGet](), p, receiver),
attempts,
true
);
2024-05-03 02:18:12 +00:00
}
const lazyTarget = target[proxyLazyGet]();
if (typeof lazyTarget === "object" || typeof lazyTarget === "function") {
return Reflect.get(lazyTarget, p, receiver);
}
2024-05-03 02:18:12 +00:00
throw new Error("proxyLazy called on a primitive value. This can happen if you try to destructure a primitive at the same tick as the proxy was created.");
}
2024-05-03 02:18:12 +00:00
}) as T;
2022-10-14 19:34:35 +00:00
}