renderer

渲染器是用于渲染 Cell 的。渲染器提供了 requestViewUpdate 方法用于外部调用,以请求视图更新,并且提供了视图的获取方法。

渲染器分为三个部分:

  • renderer.ts - Renderer 渲染器
  • scheduler.ts - Scheduler 调度器
  • queueJob.ts - JobQueue 任务队列,向任务队列添加任务,任务队列会在页面空闲时执行任务中的回调函数

Renderer

Renderer 仅仅是一个入口,是对调度器的封装,其上的 requestViewUpdate 方法是调用的调度器的 requestViewUpdate 方法,外部可以调用此方法请求视图更新。

Render 的代码非常简单,这里不做过多讲解。

Scheduler

调度器是渲染器的核心,其在内部监听模型上的 cell 增删事件,然后请求视图更新。

Scheduler 简化后定义如下:

export class Scheduler extends Disposable {
  public views: KeyValue<Scheduler.View> = {}
  public willRemoveViews: KeyValue<Scheduler.View> = {}
  protected zPivots: KeyValue<Comment>
  private graph: Graph
  private renderArea?: Rectangle
  private queue: JobQueue

  constructor(graph: Graph) {
    super()
    this.queue = new JobQueue()
    this.graph = graph
    this.init()
  }

  protected init() {
    this.startListening()
    this.renderViews(this.model.getCells())
  }

  protected startListening() {
    this.model.on('reseted', this.onModelReseted, this)
    this.model.on('cell:added', this.onCellAdded, this)
    this.model.on('cell:removed', this.onCellRemoved, this)
    this.model.on('cell:change:zIndex', this.onCellZIndexChanged, this)
    this.model.on('cell:change:visible', this.onCellVisibleChanged, this)
  }

  protected stopListening() {
    this.model.off('reseted', this.onModelReseted, this)
    this.model.off('cell:added', this.onCellAdded, this)
    this.model.off('cell:removed', this.onCellRemoved, this)
    this.model.off('cell:change:zIndex', this.onCellZIndexChanged, this)
    this.model.off('cell:change:visible', this.onCellVisibleChanged, this)
  }

  // cell 新增时会调用此方法
  protected renderViews(cells: Cell[], options: any = {}) {
    cells.sort((c1, c2) => {
      if (c1.isNode() && c2.isEdge()) {
        return -1
      }
      return 0
    })

    cells.forEach((cell) => {
      const id = cell.id
      const views = this.views
      let flag = 0
      let viewItem = views[id]

      if (viewItem) {
        flag = Scheduler.FLAG_INSERT
      } else {
        // 此处创建 cell 对应的视图
        const cellView = this.createCellView(cell)
        if (cellView) {
          cellView.graph = this.graph
          flag = Scheduler.FLAG_INSERT | cellView.getBootstrapFlag()
          viewItem = {
            view: cellView,
            flag,
            options,
            state: Scheduler.ViewState.CREATED,
          }
          this.views[id] = viewItem
        }
      }

      if (viewItem) {
        // 调用请求视图更新
        this.requestViewUpdate(
          viewItem.view,
          flag,
          options,
          this.getRenderPriority(viewItem.view),
          false,
        )
      }
    })

    this.flush()
  }

  // 最重要的方法,请求视图更新,对模型上的事件的监听函数(例如 `onModelReseted`)最终都是调用此函数
  requestViewUpdate(
    view: CellView,
    flag: number,
    options: any = {},
    priority: JOB_PRIORITY = JOB_PRIORITY.Update,
    flush = true,
  ) {
    const id = view.cell.id
    const viewItem = this.views[id]

    if (!viewItem) {
      return
    }

    viewItem.flag = flag
    viewItem.options = options

    const priorAction = view.hasAction(flag, ['translate', 'resize', 'rotate'])
    if (view.isNodeView() && priorAction) {
      priority = JOB_PRIORITY.PRIOR // eslint-disable-line
      flush = false // eslint-disable-line
    }

    // 向任务队列增加任务,任务队列会在空闲的时候执行 cb 回调
    this.queue.queueJob({
      id,
      priority,
      cb: () => {
        this.renderViewInArea(view, flag, options)
      },
    })

    // 受影响的边递归调用 requestViewUpdate
    const effectedEdges = this.getEffectedEdges(view)
    effectedEdges.forEach((edge) => {
      this.requestViewUpdate(edge.view, edge.flag, options, priority, false)
    })

    if (flush) {
      this.flush()
    }
  }

  // 渲染处于渲染范围内的视图
  protected renderViewInArea(view: CellView, flag: number, options: any = {}) {
    const cell = view.cell
    const id = cell.id
    const viewItem = this.views[id]

    if (!viewItem) {
      return
    }

    let result = 0
    // 判断是否处于渲染范围内
    if (this.isUpdateable(view)) {
      // 更新视图
      result = this.updateView(view, flag, options)
      viewItem.flag = result
    } else {
      // 已经被渲染了,那么也需要更新
      if (viewItem.state === Scheduler.ViewState.MOUNTED) {
        result = this.updateView(view, flag, options)
        viewItem.flag = result
      } else {
        // 等待进入渲染范围
        viewItem.state = Scheduler.ViewState.WAITTING
      }
    }

    if (result) {
      if (
        cell.isEdge() &&
        (result & view.getFlag(['source', 'target'])) === 0
      ) {
        this.queue.queueJob({
          id,
          priority: JOB_PRIORITY.RenderEdge,
          cb: () => {
            this.updateView(view, flag, options)
          },
        })
      }
    }
  }

  // 更新视图
  protected updateView(view: View, flag: number, options: any = {}) {
    if (view == null) {
      return 0
    }

    if (CellView.isCellView(view)) {
      if (flag & Scheduler.FLAG_REMOVE) {
        this.removeView(view.cell as any)
        return 0
      }

      if (flag & Scheduler.FLAG_INSERT) {
        this.insertView(view)
        flag ^= Scheduler.FLAG_INSERT // eslint-disable-line
      }
    }

    if (!flag) {
      return 0
    }

    // 调用视图上的 `confirmUpdate` 进行实际的渲染
    return view.confirmUpdate(flag, options)
  }
}

Schedulermodel 上监听的事件最终是调用 requestViewUpdate 方法。

首先我们关注 renderViews 方法,在节点新增时,此方法会被调用。我们可以看到当 cell.id 对应的视图不存在于缓存中时,会调用 createCellView 创建视图,此方法按照以下优先级创建对应的视图:

  • options.createCellView 如果提供了此选项,那么使用此选项创建
  • cell.view 如果在指定了 view 的名称,那么从CellView.registry 注册表中获取
  • 如果是 node,默认为 NodeView
  • 如果是 edge,默认为 EdgeView

创建视图后,最终调用 requestViewUpdate 方法,将创建的视图作为参数传入。

requestViewUpdate 方法向任务队列增加了一个任务,在回调中调用了 renderViewInArea 方法。

renderViewInArea 方法是用于渲染处于渲染范围内的视图,如果处于渲染范围内,它会调用 updateView 方法。

updateView 是更新视图的方法。如果视图被标记删除,那么会执行删除操作;如果视图需要插入,那么会执行插入操作。最后调用视图上的 confirmUpdate 方法进行实际的渲染。