• 作者:老汪软件技巧
  • 发表时间:2024-08-24 00:03
  • 浏览量:

FlagManager

标记管理器是 CellView 的一个重要属性。它用于在 CellView 渲染时,指定渲染的方式。

定义

import { KeyValue } from '@antv/x6-common'
import { CellView } from './cell'
export class FlagManager {
  // attr 表示属性,值为活动列表,被以二进制格式存储,每一个二进制位表示一个活动
  protected attrs: { [attr: string]: number }
  // name 表示活动名,值为活动对应的值,将其转换为二进制后,只有一位是 1 ,即表示此位的活动
  protected flags: { [name: string]: number }
  // 初次渲染时的活动列表
  protected bootstrap: FlagManager.Actions
  protected get cell() {
    return this.view.cell
  }
  // actions 是属性名和对应的活动列表,被解析为 attrs ,同时产生 flags
  // bootstrap 是启动时的活动
  constructor(
    protected view: CellView,
    actions: KeyValue,
    bootstrap: FlagManager.Actions = [],
  ) {
    const flags: { [name: string]: number } = {}
    const attrs: { [attr: string]: number } = {}
    let shift = 0
    Object.keys(actions).forEach((attr) => {
      let labels = actions[attr]
      if (!Array.isArray(labels)) {
        labels = [labels]
      }
      labels.forEach((label) => {
        let flag = flags[label]
        if (!flag) {
          shift += 1
          flag = flags[label] = 1 << shift
        }
        attrs[attr] |= flag
      })
    })
    let labels = bootstrap
    if (!Array.isArray(labels)) {
      labels = [labels]
    }
    labels.forEach((label) => {
      if (!flags[label]) {
        shift += 1
        flags[label] = 1 << shift
      }
    })
    // 26 - 30 are reserved for paper flags
    // 31+ overflows maximal number
    if (shift > 25) {
      throw new Error('Maximum number of flags exceeded.')
    }
    this.flags = flags
    this.attrs = attrs
    this.bootstrap = bootstrap
  }
  // 获取 label 表示的活动的二进制表示即 flag
  getFlag(label: FlagManager.Actions) {
    const flags = this.flags
    if (flags == null) {
      return 0
    }
    if (Array.isArray(label)) {
      return label.reduce((memo, key) => memo | flags[key], 0)
    }
    return flags[label] | 0
  }
  // 判断 flag 中是否存在 label 对应的活动
  hasAction(flag: number, label: FlagManager.Actions) {
    return flag & this.getFlag(label)
  }
  // 移除 flag 中的 label 对应的活动
  removeAction(flag: number, label: FlagManager.Actions) {
    return flag ^ (flag & this.getFlag(label))
  }
  // 获取启动活动对应的 flag
  getBootstrapFlag() {
    return this.getFlag(this.bootstrap)
  }
  // 获取 cell 中变化的属性对应的 flag
  getChangedFlag() {
    let flag = 0
    if (!this.attrs) {
      return flag
    }
    Object.keys(this.attrs).forEach((attr) => {
      if (this.cell.hasChanged(attr)) {
        flag |= this.attrs[attr]
      }
    })
    return flag
  }
}
export namespace FlagManager {
  // 所有可能的活动
  export type Action =
    | 'render'
    | 'update'
    | 'resize'
    | 'scale'
    | 'rotate'
    | 'translate'
    | 'ports'
    | 'tools'
    | 'source'
    | 'target'
    | 'vertices'
    | 'labels'
  export type Actions = Action | Action[]
}

attrs 表示属性对应的活动,它的值是数字,将其转换为二进制后,每一位表示一个活动,具体每位表示哪个活动,由 flags 决定,flags 的 key 是活动,值是活动对应的值。bootstrap 是启动时的活动。

示例

直接理解可能较为困难,我们结合一个示例来理解,在 CellView 的 constructor 中,flag 被初始化,如下:

this.flag = new FlagManager(
    this,
    this.options.actions,
    this.options.bootstrap,
)

如果其中的 options.actions 为:

{
    "view": ["render"],
    "markup": ["render"],
    "attrs": ["update"],
    "size": ["resize", "ports", "tools"],
    "angle": ["rotate", "tools"],
    "position": ["translate", "tools"],
    "ports": ["ports"],
    "tools": ["tools"]
}

那么,经过初始化后,flag.attrs 为:

{
    "view": 2,
    "markup": 2,
    "attrs": 4,
    "size": 56, // 8 + 16 + 32
    "angle": 96, // 64 + 32
    "position": 160, // 128 + 32
    "ports": 16,
    "tools": 32
}

flag.flags 为:

{
    "render": 2,
    "update": 4,
    "resize": 8,
    "ports": 16,
    "tools": 32,
    "rotate": 64,
    "translate": 128
}

作用

在视图章节,我们知道了 confirmUpdate 是视图渲染的入口函数。它的第一个参数就是 flag, 视图根据 flag 中的活动来判断到底如何渲染。

NodeView

例如,在 NodeView 中,confirmUpdate 定义如下:

  confirmUpdate(flag: number, options: any = {}) {
    let ret = flag
    if (this.hasAction(ret, 'ports')) {
      this.removePorts()
      this.cleanPortsCache()
    }
    if (this.hasAction(ret, 'render')) {
      this.render()
      ret = this.removeAction(ret, [
        'render',
        'update',
        'resize',
        'translate',
        'rotate',
        'ports',
        'tools',
      ])
    } else {
      ret = this.handleAction(
        ret,
        'resize',
        () => this.resize(),
        'update', // Resize method is calling `update()` internally
      )
      ret = this.handleAction(
        ret,
        'update',
        () => this.update(),
        // `update()` will render ports when useCSSSelectors are enabled
        Config.useCSSSelector ? 'ports' : null,
      )
      ret = this.handleAction(ret, 'translate', () => this.translate())
      ret = this.handleAction(ret, 'rotate', () => this.rotate())
      ret = this.handleAction(ret, 'ports', () => this.renderPorts())
      ret = this.handleAction(ret, 'tools', () => {
        if (this.getFlag('tools') === flag) {
          this.renderTools()
        } else {
          this.updateTools(options)
        }
      })
    }
    return ret
  }

flag 中存在 ports 时,先移除所有 ports,然后根据 flag 中是否存在 render 来决定是调用 render 函数全量渲染,还是调用各种对应的函数来更新。

EdgeView

EdgeView 中的 confirmUpdate 定义如下:

  confirmUpdate(flag: number, options: any = {}) {
    let ref = flag
    if (this.hasAction(ref, 'source')) {
      if (!this.updateTerminalProperties('source')) {
        return ref
      }
      ref = this.removeAction(ref, 'source')
    }
    if (this.hasAction(ref, 'target')) {
      if (!this.updateTerminalProperties('target')) {
        return ref
      }
      ref = this.removeAction(ref, 'target')
    }
    const graph = this.graph
    const sourceView = this.sourceView
    const targetView = this.targetView
    if (
      graph &&
      ((sourceView && !graph.renderer.isViewMounted(sourceView)) ||
        (targetView && !graph.renderer.isViewMounted(targetView)))
    ) {
      // Wait for the sourceView and targetView to be rendered.
      return ref
    }
    if (this.hasAction(ref, 'render')) {
      this.render()
      ref = this.removeAction(ref, ['render', 'update', 'labels', 'tools'])
      return ref
    }
    ref = this.handleAction(ref, 'update', () => this.update(options))
    ref = this.handleAction(ref, 'labels', () => this.onLabelsChange(options))
    ref = this.handleAction(ref, 'tools', () => this.renderTools())
    return ref
  }

首先 flag 中存在 source 时更新 source,flag 中存在 target 时更新 target,然后同样是根据 flag 中是否存在 render 来决定是调用 render 函数全量渲染,还是调用各种对应的函数来更新。