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
|
|
|
|
2023-09-23 01:25:19 +00:00
|
|
|
export function makeLazy<T>(factory: () => T, attempts = 5): () => T {
|
|
|
|
let tries = 0;
|
2023-05-05 23:36:00 +00:00
|
|
|
let cache: T;
|
2023-09-23 01:25:19 +00:00
|
|
|
return () => {
|
|
|
|
if (!cache && attempts > tries++) {
|
|
|
|
cache = factory();
|
2024-05-03 02:18:12 +00:00
|
|
|
if (!cache && attempts === tries) {
|
|
|
|
console.error(`Lazy factory failed:\n${factory}`);
|
|
|
|
}
|
2023-09-23 01:25:19 +00:00
|
|
|
}
|
|
|
|
return cache;
|
|
|
|
};
|
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
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-05-03 02:18:12 +00:00
|
|
|
const proxyLazyGet = Symbol.for("vencord.lazy.get");
|
|
|
|
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);
|
|
|
|
|
2023-11-22 05:48:59 +00:00
|
|
|
let isSameTick = true;
|
2024-05-03 02:18:12 +00:00
|
|
|
if (!isChild) setTimeout(() => isSameTick = false, 0);
|
2023-11-22 05:48:59 +00:00
|
|
|
|
2024-05-03 02:18:12 +00:00
|
|
|
let failed = false;
|
2023-07-05 22:53:43 +00:00
|
|
|
const proxyDummy = Object.assign(function () { }, {
|
2024-05-03 02:18:12 +00:00
|
|
|
[proxyLazyGet]() {
|
|
|
|
if (!proxyDummy[proxyLazyCache] && !failed) {
|
|
|
|
proxyDummy[proxyLazyCache] = get();
|
|
|
|
|
|
|
|
if (!proxyDummy[proxyLazyCache]) {
|
|
|
|
failed = true;
|
|
|
|
throw new Error(`proxyLazy factory failed:\n${factory}`);
|
|
|
|
}
|
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
|
2023-02-09 20:21:14 +00:00
|
|
|
});
|
2022-11-06 17:00:59 +00:00
|
|
|
|
2023-11-22 05:48:59 +00:00
|
|
|
return new Proxy(proxyDummy, {
|
|
|
|
...handler,
|
|
|
|
get(target, p, receiver) {
|
2024-05-03 02:18:12 +00:00
|
|
|
// If we're still in the same tick, it means the lazy was immediately used.
|
2023-11-22 05:48:59 +00:00
|
|
|
// 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) {
|
2023-11-22 05:48:59 +00:00
|
|
|
return proxyLazy(
|
2024-05-03 02:18:12 +00:00
|
|
|
() => Reflect.get(target[proxyLazyGet](), p, receiver),
|
2023-11-22 05:48:59 +00:00
|
|
|
attempts,
|
|
|
|
true
|
|
|
|
);
|
2024-05-03 02:18:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const lazyTarget = target[proxyLazyGet]();
|
2024-04-08 04:33:35 +00:00
|
|
|
if (typeof lazyTarget === "object" || typeof lazyTarget === "function") {
|
|
|
|
return Reflect.get(lazyTarget, p, receiver);
|
|
|
|
}
|
2024-05-03 02:18:12 +00:00
|
|
|
|
2024-04-08 04:33:35 +00:00
|
|
|
throw new Error("proxyLazy called on a primitive value");
|
2023-11-22 05:48:59 +00:00
|
|
|
}
|
2024-05-03 02:18:12 +00:00
|
|
|
}) as T;
|
2022-10-14 19:34:35 +00:00
|
|
|
}
|