ECS

Why ECS?

ECS (Entity-Component-System)

ECS is suitable for decoupling large data objects, commonly used in games where each character (Entity) has extensive data that needs to be split into physics engine-related data, skin-related data, character attributes, etc. (multiple Components) for consumption by different subsystems (Systems). When workflow data structures are complex, ECS is very suitable for decomposition.

Solution Comparison

Let's compare two data solutions:

1. ReduxStore Solution

const store = () => ({
  nodes: [{
    position: any
    form: any
    data3: any

  }],
  edges: []
})

function Playground() {
  const { nodes } = useStore(store)

  return nodes.map(node => <Node data={node} />)
}

Advantages:

  • Simple to use with centralized data management

Disadvantages:

  • Centralized data management cannot update precisely, leading to performance bottlenecks
  • Poor extensibility, adding new node data couples everything into one large JSON

2. ECS Solution

Notes:

  • NodeData corresponds to ECS - Component
  • Layer corresponds to ECS - System
/**
 * Canvas document data
 */
class FlowDocument {
  /**
   * Node data definitions, node data will be instantiated when created
   */
  nodeDefines: [
    NodePositionData,
    NodeFormData,
    NodeLineData
  ]
  nodeEntities: Entity[] = []
}

/**
 * Node
 */
class FlowNodeEntity {
  id: string // Only has id, no data
  getData: (dataId: string) => EntityData
}

// Render lines
class LinesLayer {
  /**
   * Internally gets corresponding data via node.getData(NodeLineData), same below
   */
  @observeEntityData(FlowNodeEntity, NodeLineData) lines: NodeLineData[]
  render() {
    // Render lines
    return this.lines.map(line => <Line data={line} />)
  }
}

// Render node positions
class NodePositionsLayer {
  @observeEntityData(FlowNodeEntity, NodePositionData) positions: NodePositionData[]
  render() {
    // Render positions and layout
  }
}

// Render node forms
class NodeFormsLayer {
  @observeEntityData(FlowNodeEntity, NodeFormData) contents: NodeFormData[]
  render() {
    // Render node content
  }
}

/**
 * Canvas instance, renders in layers via Layer
 */
class Playground {
  layers: [
    LinesLayer, // Line rendering
    NodePositionsLayer, // Position rendering
    NodeFormsLayer // Content rendering
  ],
  render() {
    // Canvas layer rendering
    return this.layers.map(layer => layer.render())
  }
}

Advantages:

  • Node data is split for individual rendering control, enabling precise performance updates
  • Strong extensibility - adding new node data just requires adding a new XXXData + XXXLayer

Disadvantages:

  • Has a certain learning curve