chore: initial commit

This commit is contained in:
2026-10-22 17:07:08 +08:00
commit 0acef64f48
200 changed files with 35546 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
import {performance} from 'node:perf_hooks';
import type {Bot} from '@wp2508/mineflayer';
import type {BaseInfo, InfoI18n} from './types.ts';
type Meter = {depth: number; total: number};
const meters = new WeakMap<object, Meter>();
/** Only the synchronous invocation is timed, even when it returns a Promise. */
export function measure_sync<T>(bot: object, invoke: () => T): T {
const meter = meters.get(bot);
if (!meter) return invoke();
const outer = meter.depth++ === 0;
const start = outer ? performance.now() : 0;
try { return invoke(); }
finally {
meter.depth--;
if (outer) meter.total += performance.now() - start;
}
}
export function instrument_bot(bot: Bot): {total: () => number; dispose: () => void} {
if (meters.has(bot)) throw new Error('Bot resource meter already installed');
const meter: Meter = {depth: 0, total: 0};
meters.set(bot, meter);
const restore: (() => void)[] = [];
try {
for (const target of new Set([bot, bot._client])) {
if (!target || typeof target.emit !== 'function') continue;
const descriptor = Object.getOwnPropertyDescriptor(target, 'emit');
const original = target.emit;
const wrapper = function(this: typeof target, ...args: Parameters<typeof original>) {
return measure_sync(bot, () => Reflect.apply(original, this, args));
};
Object.defineProperty(target, 'emit', {configurable: true, writable: true,
enumerable: descriptor?.enumerable ?? false, value: wrapper});
restore.push(() => {
if (Object.getOwnPropertyDescriptor(target, 'emit')?.value !== wrapper) return;
if (descriptor) Object.defineProperty(target, 'emit', descriptor);
else delete (target as unknown as {emit?: unknown}).emit;
});
}
} catch (error) {
for (const undo of restore.reverse()) undo();
meters.delete(bot);
throw error;
}
let disposed = false;
return {total: () => meter.total, dispose: () => {
if (disposed) return;
disposed = true;
meters.delete(bot);
for (const undo of restore.reverse()) undo();
}};
}
export function metadata(name: string, description: string, fields: Record<string, readonly [string, string]>): InfoI18n {
return {lang: 'zh-CN', name, description, fields: Object.fromEntries(Object.entries(fields).map(
([key, [name, description]]) => [key, {lang: 'zh-CN', name, description}],
))};
}
/** No module owns bot end listeners; InfoManager owns disposal. */
export abstract class ResourceInfo<T> implements BaseInfo<T> {
abstract readonly name: string;
abstract readonly i18n: InfoI18n;
protected disposed = false;
private timer?: ReturnType<typeof setInterval>;
private running = false;
private readonly listeners = new Set<(value: T) => void>();
constructor(protected info: T) {}
get_info(): T { return this.info; }
on_update(callback: (value: T) => void): () => void {
if (this.disposed) throw new Error('Resource info disposed');
this.listeners.add(callback);
return () => { this.listeners.delete(callback); };
}
protected publish(info: T): void {
if (this.disposed) return;
this.info = info;
for (const listener of Array.from(this.listeners)) {
if (this.disposed) break;
try { listener(info); }
catch (error) { console.error('Resource info listener failed', error); }
}
}
protected start(interval: number, sample: () => void | Promise<void>, immediate = false): void {
const run = async () => {
if (this.running || this.disposed) return;
this.running = true;
try { await sample(); }
catch (error) { if (!this.disposed) console.error('Resource sampling failed', error); }
finally { this.running = false; }
};
this.timer = setInterval(() => { void run(); }, interval);
this.timer.unref?.();
if (immediate) void run();
}
dispose(): void {
if (this.disposed) return;
this.disposed = true;
clearInterval(this.timer);
this.listeners.clear();
}
}