414 lines
9.3 KiB
TypeScript
414 lines
9.3 KiB
TypeScript
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 = [] as T[];
|
|
private 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(min: Vec2, max: Vec2) {
|
|
if (!range(min.x(), max.x()).includes(this.x())) return false;
|
|
if (!range(min.y(), max.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;
|
|
}
|
|
|
|
public clone() {
|
|
return new Vec2(this.x(), this.y());
|
|
}
|
|
|
|
public normalize() {
|
|
return new Vec2(clamp(this.x(), -1, 1), clamp(this.y(), -1, 1));
|
|
}
|
|
}
|
|
|
|
export function clamp(x: number, min: number, max: number) {
|
|
return Math.max(min, Math.min(x, max));
|
|
}
|
|
|
|
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, stdin: "inherit", 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 } };
|
|
// deno-lint-ignore no-explicit-any
|
|
export type Constructible<I = unknown> = new (...args: any[]) => 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;
|
|
}
|
|
|
|
has<I extends B, C extends Constructible<I>>(class_: C) {
|
|
return this.inner.has(class_.prototype);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
export type Awaitable<T = unknown> = T | Promise<T>;
|
|
|
|
export function first<T>(iter: Iterable<T>) {
|
|
for (const item of iter) return item;
|
|
return null;
|
|
}
|
|
|
|
export function take_n<T>(iter: Iterable<T>, n: number) {
|
|
const result = [] as T[];
|
|
for (const item of iter) {
|
|
if (result.length >= n) break;
|
|
result.push(item);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function* line(from: Vec2, to: Vec2) {
|
|
let current = from.clone();
|
|
while (current.neq(to)) {
|
|
yield current;
|
|
if (current.x() - to.x() > 0.5) current = current.sub(v2(1, 0));
|
|
if (current.x() - to.x() < -0.5) current = current.add(v2(1, 0));
|
|
if (current.y() - to.y() > 0.5) current = current.sub(v2(0, 1));
|
|
if (current.y() - to.y() < -0.5) current = current.add(v2(0, 1));
|
|
}
|
|
}
|
|
|
|
export function* spiral(origin: Vec2) {
|
|
yield origin;
|
|
for (const i of range(0, 999)) {
|
|
const size = i * 2 + 1;
|
|
const corner_A = origin.add(v2(-size / 2, -size / 2));
|
|
const corner_B = origin.add(v2(size / 2, -size / 2));
|
|
const corner_C = origin.add(v2(size / 2, size / 2));
|
|
const corner_D = origin.add(v2(-size / 2, size / 2));
|
|
yield* line(corner_A, corner_B);
|
|
yield* line(corner_B, corner_C);
|
|
yield* line(corner_C, corner_D);
|
|
yield* line(corner_D, corner_A);
|
|
}
|
|
}
|
|
|
|
// Deno.test("test_spiral", () => {
|
|
// // 2 | 10 11 12 13 14
|
|
// // 1 | 25 2 3 4 15
|
|
// // 0 | 24 9 1 5 16
|
|
// // -1 | 23 8 7 6 17
|
|
// // -2 | 22 21 20 19 18
|
|
// // ---+---------------
|
|
// // -2 -1 0 1 2
|
|
// const result = [...take_n(spiral(v2(0, 0)), 25)];
|
|
// const expects = [
|
|
// v2(0, 0),
|
|
// v2(1, 1),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// v2(),
|
|
// ]
|
|
// });
|