All files / src/workers NativeCommunicator.ts

81.57% Statements 31/38
68.75% Branches 11/16
81.81% Functions 9/11
88.57% Lines 31/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93              7x 7x 7x   7x           7x   7x         7x       7x 4x 4x 4x   4x 4x 4x 4x 4x             7x 1x               1x     1x             3x 3x               1x   1x 1x   1x 1x 1x 1x     1x         1x        
import { ChildProcess, spawn } from "node:child_process";
 
/**
 * 2026 Zenith Tier: OS ネイティブバイナリと通信するためのコミュニケーター。
 * Node.js/Bun 環境でのみ動作し、child_process を使用します。
 */
export class NativeCommunicator {
  private child: ChildProcess | null = null;
  private messageListeners: Set<(data: unknown) => void> = new Set();
  private buffer = "";
 
  constructor(private readonly binaryPath: string) {}
 
  /**
   * エンジンプロセスを起動します。
   */
  async spawn(): Promise<void> {
    Iif (this.child) return;
 
    this.child = spawn(this.binaryPath, [], {
      stdio: ["pipe", "pipe", "pipe"],
      windowsHide: true,
    });
 
    Iif (!this.child.stdout || !this.child.stdin) {
      throw new Error("Failed to initialize engine process streams.");
    }
 
    this.child.stdout.on("data", (chunk: Buffer) => {
      this.buffer += chunk.toString();
      const lines = this.buffer.split("\n");
      this.buffer = lines.pop() || "";
 
      for (const line of lines) {
        const trimmed = line.trim();
        Eif (trimmed) {
          for (const listener of this.messageListeners) {
            listener(trimmed);
          }
        }
      }
    });
 
    // プロセス終了時のクリーンアップ
    this.child.on("exit", () => {
      this.child = null;
    });
  }
 
  /**
   * エンジンにメッセージを送信します。
   */
  postMessage(message: string): void {
    Iif (!this.child || !this.child.stdin) {
      throw new Error("NativeCommunicator not connected.");
    }
    this.child.stdin.write(`${message}\n`);
  }
 
  /**
   * メッセージ受信リスナーを登録します。
   */
  onMessage(callback: (data: unknown) => void): () => void {
    this.messageListeners.add(callback);
    return () => this.messageListeners.delete(callback);
  }
 
  /**
   * エンジンプロセスを物理的に終了し、完全に停止するまで待機します。
   * (2026 Zenith Tier: Guaranteed Deterministic Cleanup)
   */
  async terminate(): Promise<void> {
    Iif (!this.child) return;
 
    const child = this.child;
    this.child = null;
 
    return new Promise((resolve) => {
      const timer = setTimeout(() => {
        child.kill("SIGKILL");
        resolve();
      }, 2000); // 2秒待機して強制終了
 
      child.on("exit", () => {
        clearTimeout(timer);
        resolve();
      });
 
      child.kill("SIGTERM");
    });
  }
}