唯杰地图 logo唯杰地图
VJMAP
EN

state-machine

vjcad/references/command/state-machine.md 4.8 KB129 行
原文

本文回答:多步骤交互命令(如 PLINE)建议把 step / state 当字段,async main() 写一个 while (this.step > 0) { switch(this.step) {...} } 循环,每步是一个 async stepN_xxx() 方法;用 keywords 接收 C(close) / U(undo) 这类关键字;getPoint 空输入回车/空格返回 InputStatusEnum.None(5000),据此判"完成"(EnterOrSpace 只由 getEntity 返回)。

一、PLINE 风格的状态机模板

class MyPlineCommand {
  constructor() {
    this.step = 1;
    this.points = [];
    this.isClosed = false;
  }

  async main() {
    Engine.undoManager.start_undoMark('绘制多段线');
    try {
      while (this.step > 0) {
        switch (this.step) {
          case 1: await this.step1_getFirstPoint(); break;
          case 2: await this.step2_getNextPoints(); break;
          case 3: this.step3_finish();              break;
        }
      }
    } finally {
      Engine.undoManager.end_undoMark();
      Engine.clearPreview();
    }
  }

  async step1_getFirstPoint() {
    const opt = new PointInputOptions('指定起点:');
    const r = await Engine.getPoint(opt);
    if (r.status === InputStatusEnum.OK) {
      this.points.push(r.value);
      this.step = 2;
    } else {
      this.step = 0;
    }
  }

  async step2_getNextPoints() {
    const lastPt = this.points[this.points.length - 1];
    const opt = new PointInputOptions('指定下一点 [闭合(C)/撤销(U)] <完成>:');
    opt.keywords = ['C', 'U'];
    opt.useBasePoint = true;
    opt.basePoint = lastPt;
    opt.callback = (canvasPt) => {
      const wp = Engine.canvasToWcs(canvasPt);
      this.drawPreview(wp);
    };

    const r = await Engine.getPoint(opt);

    if (r.status === InputStatusEnum.OK) {
      this.points.push(r.value);
    } else if (r.stringResult === 'C') {
      if (this.points.length >= 3) { this.isClosed = true; this.step = 3; }
      else writeMessage('<br/>至少需要3个点才能闭合');
    } else if (r.stringResult === 'U') {
      if (this.points.length > 1) { this.points.pop(); }
      else this.step = 1;
    } else if (r.status === InputStatusEnum.None) {   // 空输入回车/空格 = 完成(getPoint 不返回 EnterOrSpace)
      this.step = 3;
    } else {
      this.step = 0;
    }
  }

  step3_finish() {
    if (this.points.length >= 2) {
      const pl = new PolylineEnt();
      for (const pt of this.points) pl.addVertex([pt.x, pt.y]);
      pl.isClosed = this.isClosed;
      pl.setDefaults();
      Engine.addEntities(pl);
    }
    this.step = 0;
  }

  drawPreview(currentPt) {
    if (this.points.length === 0) return;
    const pl = new PolylineEnt();
    for (const pt of this.points) pl.addVertex([pt.x, pt.y]);
    pl.addVertex([currentPt.x, currentPt.y]);
    pl.setDefaults();
    Engine.clearPreview();
    Engine.drawPreviewEntity(pl);
  }
}

MCP 参考:vjcad/example/command/04statemachine.js。

二、状态机关键点

  1. 退出靠 step = 0:把 0 作为 sentinel,while (this.step > 0) 自然退出。
  2. 关键字判定靠 result.stringResult:PointInputOptions.keywords = ['C', 'U'] 配合 result.status === InputStatusEnum.Keyword(或部分实现里直接用 result.stringResult)。
  3. 回车 / 空格判定:getPoint 空输入回车/空格返回 InputStatusEnum.None(5000),判"完成"写 r.status === InputStatusEnum.None(内置 LINE 命令同此写法);EnterOrSpace(3)只由 getEntity 返回,拿它判 getPoint 的回车永远不成立(值见 input/get-string-number 的 InputStatusEnum 表)。Esc 返回 InputStatusEnum.Cancel,不抛异常。
  4. 预览必须每次清:Engine.clearPreview() + Engine.drawPreviewEntity(...),命令结束 finally 再 clear 一次。

三、文档里的另一种范式(按状态字段)

class MultiStepCommand {
  state = 'START';
  async main() {
    while (this.state !== 'END') {
      switch (this.state) {
        case 'START':   await this.handleStart();   break;
        case 'SELECT':  await this.handleSelect();  break;
        case 'CONFIRM': await this.handleConfirm(); break;
      }
    }
  }
}

MCP 参考:vjcad/doc/command/index.md。

反例

  • 在 step2 里循环取点但忘记 points.length === 0 判断,导致 lastPt = undefined。
  • 关键字大小写没统一:opt.keywords = ['C'] 但比对时写 r.stringResult === 'c',会一直走不到。
  • 用 while(true) 死循环但没在 step = 0 时 break / return。

参考(官方文档 MCP)

  • 文档:vjcad/doc/command/index.md
  • 示例:vjcad/example/command/04statemachine.js、vjcad/example/input/06getkeyword.js