This commit is contained in:
Matthieu Jolimaitre 2024-04-09 02:05:02 +02:00
commit 28b026a614
17 changed files with 895 additions and 0 deletions

322
common/utils.ts Normal file
View file

@ -0,0 +1,322 @@
import { assert, assertEquals, assertInstanceOf } from "https://deno.land/std@0.221.0/assert/mod.ts";
import * as path from "https://deno.land/std@0.221.0/path/mod.ts";
import { TextLineStream } from "https://deno.land/std@0.221.0/streams/mod.ts";
import { z, ZodType } from "https://deno.land/x/zod@v3.22.4/mod.ts";
export function parsed_stream<T>(parser: ZodType<T>) {
return async function* (readable: ReadableStream<Uint8Array>) {
const line_stream = readable
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TextLineStream());
for await (const line of line_stream) yield parser.parse(JSON.parse(line));
};
}
export function serialized_stream<T>(generator: AsyncIterable<T>) {
return async function (writable: WritableStream<Uint8Array>) {
const stream = new TextEncoderStream();
stream.readable.pipeTo(writable);
for await (const item of generator) {
const writer = stream.writable.getWriter();
await writer.write(JSON.stringify(item) + "\n");
writer.releaseLock();
}
};
}
export type Consumer<T> = (item: T) => void;
// deno-lint-ignore no-explicit-any
export type ParserOutput<T extends () => ZodType<any, any, any>> = z.infer<ReturnType<T>>;
export type PromiseSplit<T> = ReturnType<typeof split_promise<T>>;
export function split_promise<T>() {
let resolver_ = null as Consumer<T> | null;
const promise = new Promise<T>((r) => {
resolver_ = r;
});
assert(resolver_ !== null);
const resolver = resolver_ as Consumer<T>;
return { promise, resolver };
}
export class AsyncQueue<T> {
private items;
private awaiting;
public constructor() {
this.items = [] as T[];
this.awaiting = [] as Consumer<void>[];
}
public push(item: T) {
this.items.push(item);
const [first] = this.awaiting.splice(0, 1);
if (first !== undefined) first();
}
public async pull() {
let first = this.take_first_item();
if (first !== undefined) return first;
const { promise, resolver } = split_promise<void>();
this.awaiting.push(resolver);
await promise;
first = this.take_first_item();
assert(first !== undefined);
return first as T;
}
private take_first_item() {
const [first] = this.items.splice(0, 1);
return first as T | undefined;
}
}
export function channel<T>() {
const queue = new AsyncQueue<T>();
return [new Sender(queue), new Receiver(queue)] as const;
}
export function channel_generator<T>(generator: AsyncIterable<T>) {
const [sender, receiver] = channel();
(async () => {
for await (const item of generator) sender.send(item);
})();
return receiver;
}
export class Sender<T> {
queue;
public constructor(queue: AsyncQueue<T>) {
this.queue = queue;
}
public send(item: T) {
this.queue.push(item);
}
public async send_all(iter: AsyncGenerator<T>) {
for await (const item of iter) this.send(item);
}
}
export class Receiver<T> {
queue;
public constructor(queue: AsyncQueue<T>) {
this.queue = queue;
}
public async receive() {
return await this.queue.pull();
}
public async *iter() {
while (true) yield this.receive();
}
}
export function range(from: number, to: number) {
return new Range(from, to);
}
export class Range {
from;
to;
constructor(from: number, to: number) {
this.from = from;
this.to = to;
}
includes(n: number) {
return (n >= this.from) && (n < this.to);
}
[Symbol.iterator]() {
let index = this.from;
const to = this.to;
return (function* () {
while (index < to) yield index++;
})();
}
}
export async function launch_caught<T>(
operation: () => Promise<T> | T,
handler: null | ((error: unknown) => void) = null,
) {
try {
return await operation();
} catch (error) {
if (handler !== null) handler(error);
else console.error("Lanched Uncaught :", error);
}
}
export function log_from(meta: ImportMeta) {
return function (...args: unknown[]) {
const mod = path.basename(new URL(meta.url).pathname);
console.log(`[${mod}]`, ...args);
};
}
export class Vec2 {
private x_;
private y_;
public constructor(x: number, y: number) {
this.x_ = Math.round(x);
this.y_ = Math.round(y);
}
public x() {
return this.x_;
}
public y() {
return this.y_;
}
public scale(factor: number) {
return new Vec2(this.x_ * factor, this.y_ * factor);
}
public neg() {
return this.scale(-1);
}
public add(other: Vec2) {
return new Vec2(this.x_ + other.x_, this.y_ + other.y_);
}
public sub(other: Vec2) {
return this.add(other.neg());
}
public inside(corner_tl: Vec2, corner_br: Vec2) {
if (!range(corner_tl.x(), corner_br.x()).includes(this.x())) return false;
if (!range(corner_tl.y(), corner_br.y()).includes(this.y())) return false;
return true;
}
public eq(other: Vec2) {
return (this.x() === other.x()) && (this.y() === other.y());
}
public neq(other: Vec2) {
return !this.eq(other);
}
public len() {
return Math.sqrt(this.x() ** 2 + this.y() ** 2);
}
public distance(other: Vec2) {
return this.sub(other).len();
}
public overlaps(other: Vec2) {
return this.distance(other) < 1;
}
}
export function v2(x: number, y: number) {
return new Vec2(x, y);
}
export function* enumerate<T>(iterator: Iterable<T>) {
let index = 0;
for (const item of iterator) yield [index++, item] as [number, T];
}
export function* chunks<T>(iterator: Iterable<T>, size: number) {
let current = [];
for (const item of iterator) {
current.push(item);
if (current.length < size) continue;
yield current;
current = [];
}
}
Deno.test("test_chunk", () => {
assertEquals(
[...chunks([1, 2, 3, 4, 5, 6, 7], 3)],
[[1, 2, 3], [4, 5, 6]],
);
});
export function wait(ms: number) {
return new Promise((res) => setTimeout(res, ms));
}
export async function run(cmd: string, ...args: string[]) {
const command = new Deno.Command(cmd, { args, stdout: "piped" });
const output = await command.output();
if (!output.success) throw new Error();
return new TextDecoder().decode(output.stdout);
}
export type Prototyped<P = unknown> = { constructor: { prototype: P } };
export type Constructible<I = unknown> = new (...args: unknown[]) => I;
export class ClassMap<B = unknown> {
private inner;
constructor() {
this.inner = new Map();
}
insert<V extends Prototyped & B>(object: V) {
const prototype = object.constructor.prototype;
if (this.inner.has(prototype)) throw new Error();
this.inner.set(prototype, object);
}
get<I extends B, C extends Constructible<I>>(class_: C) {
const value = this.inner.get(class_.prototype);
if (value === undefined) return null;
assertInstanceOf(value, class_);
return value;
}
}
Deno.test("test_classmap", () => {
{
class Truc {}
class Machin {}
class Chose {}
const map = new ClassMap();
const truc = new Truc();
map.insert(truc);
const machin = new Machin();
map.insert(machin);
assert(map.get(Truc) === truc);
assert(map.get(Machin) !== truc);
assert(map.get(Machin) === machin);
assert(map.get(Chose) === null);
}
{
interface Machinable {
machin(): number;
}
// deno-lint-ignore no-unused-vars
class Truc {}
class Machin implements Machinable {
machin = () => 35;
}
const map = new ClassMap<Machinable>();
map.insert(new Machin());
// map.insert(new Truc()); // note : Fails with improper typing.
map.get(Machin);
// map.get(Truc); // note : Fails with improper typing.
}
});
export function maybe<T>(item: T) {
return item as T | undefined;
}