UNPKG

@haelp/teto

Version:

A typescript-based controllable TETR.IO client.

132 lines (131 loc) 3.89 kB
import { EventEmitter } from "../index.mjs"; import { Adapter } from "./core/index.mjs"; import chalk from "chalk"; import { spawn } from "child_process"; /** * Communicates with a binary engine executable using Standard Input/Output. * Uses JSON messages. * @see {@link Messages.CustomMessageData} */ export class AdapterIO extends Adapter { static defaultConfig = { name: "AdapterIO", verbose: false, env: {}, args: [] }; log(msg, { force = false, level = "info" } = { force: false, level: "info" }) { if (!this.cfg.verbose && !force) return; const func = level === "info" ? chalk.blue : level === "warning" ? chalk.yellow : chalk.red; console[level === "error" ? "error" : "log"](`${func(`[${this.cfg.name}]`)} ${msg}`); } cfg; events = new EventEmitter(); dead = false; #dataBuffer = ""; process; constructor(config){ super(); if (!config.path) { throw new Error("AdapterIO requires a path to the binary executable."); } this.cfg = { ...AdapterIO.defaultConfig, ...config }; } #start() { this.process = spawn(this.cfg.path, this.cfg.args, { env: this.cfg.env, stdio: [ "pipe", "pipe", "pipe" ] }); this.process.stderr.on("data", (data)=>{ this.log("Error: " + data.toString(), { level: "error", force: true }); }); this.process.stdout.on("data", (message)=>this.#handle(message)); } initialize() { return new Promise((resolve)=>{ this.events.once("info", (info)=>{ this.log("READY"); resolve(info); }); this.#start(); }); } send(message) { if (this.dead) return; this.process.stdin.write(JSON.stringify(message) + "\n"); this.log("OUTGOING:\n" + JSON.stringify(message, null, 2)); } #handle(message) { const text = message.toString(); this.#dataBuffer += text; let newlineIndex; while((newlineIndex = this.#dataBuffer.indexOf("\n")) >= 0){ const line = this.#dataBuffer.slice(0, newlineIndex); this.#dataBuffer = this.#dataBuffer.slice(newlineIndex + 1); if (line === "") continue; try { const message = JSON.parse(line.trim()); this.log("INCOMING:\n" + JSON.stringify(message, null, 2)); this.events.emit(message.type, message); } catch { this.log(line); } } } config(engine, data) { this.send({ type: "config", ...this.configFromEngine(engine), data }); } update(engine, data) { this.send({ type: "state", ...this.stateFromEngine(engine), data }); } addPieces(pieces, data) { this.send({ type: "pieces", pieces, data }); } play(engine, data) { this.send({ type: "play", ...this.playFromEngine(engine), data }); return new Promise((resolve)=>{ this.events.once("move", (move)=>{ resolve(move); }); }); } stop() { this.process.kill("SIGINT"); this.events.removeAllListeners(); this.process.removeAllListeners(); this.process.stdout.removeAllListeners(); this.process.stderr.removeAllListeners(); this.process.stdin.removeAllListeners(); this.process.unref(); this.dead = true; } } //# sourceMappingURL=adapter-io.js.map