Двигун - Rete.js

Двигун

Архітектура

rete-engine — це пакет, який реалізує два підходи до обробки графів: Dataflow і Control flow

Dataflow

Підхід потоку даних зосереджений виключно на даних, де цільовий вузол запитує дані від вхідних вузлів. Обробка графа відбувається зліва направо, передаючи вихідні дані вузлів як вхідні аргументи до вихідних вузлів.

Dataflow

Цей підхід зазвичай використовується в продуктах з редакторами вузлів, таких як Blender.

Наведений нижче код представляє основні конструкції, необхідні для роботи DataflowEngine:

  • Вузли повинні реалізовувати метод data: цей метод приймає дані від вхідних вузлів
  • Підключіть двигун до редактора: двигун зареєструє кожен доданий вузол для подальшої обробки
  • Отримання даних вузла: fetch ініціює обхід графа, починаючи з цільового вузла та відвідуючи всі його попередники
ts
import { ClassicPreset } from 'rete-engine'
import { DataflowEngine } from 'rete-engine'

const { Node, Socket } = ClassicPreset

class NodeAdd extends Node<{ left: Socket, right: Socket }, { value: Socket }, { }> {

  constructor() {
    // ініціалізувати елементи керування та порти
  }

  // обов'язковий метод
  data(inputs: { left?: number[], right?: number[] }): { value: number } {
    const left = inputs.left[0] || 0
    const right = inputs.right[0] || 0

  return {
      value: left + right
    }
  }
}

const engine = new DataflowEngine<Schemes>()

editor.use(engine)

const nodeOutput = await engine.fetch(resultNode.id)

Control flow

Потік керування — це підхід обходу вузлів, який дозволяє визначити спосіб передавати керування наступним вузлам.

Control flow

Обробка починається з початкового вузла, який визначає, як контроль передається через його вихідні з'єднання. Наприклад, це може бути затримка або розгалуження. Найближчим прикладом є UE4 Blueprints.

ts
import { ControlFlowEngine } from 'rete-engine'

const { Node, Socket } = ClassicPreset

class Log extends Node<{ enter: Socket }, { out: Socket }, {}> {
  constructor() {
    // ініціалізувати порти
  }

  // обов'язковий метод
  execute(input: 'enter', forward: (output: 'out') => void) {
    console.log('log something')
    forward('out')
  }
}


const engine = new ControlFlowEngine<Schemes>()

editor.use(engine)

engine.execute(startNode.id)

Гібрид

Крім того, ці підходи можна комбінувати. Наприклад, порти з назвою 'exec' можна використовувати для керування потоком, тоді як інші порти керують даними.

ts
const controlflow = new ControlFlowEngine<Schemes>(node => {
  return {
    inputs: () => ['exec'],
    outputs: () => ['exec']
  }
})
const dataflow = new DataflowEngine<Schemes>(({ inputs, outputs }) => {
  return {
    inputs: () => Object.keys(inputs).filter(name => name !== 'exec'),
    outputs: () => Object.keys(outputs).filter(name => name !== 'exec')
  }
})

Крім того, ви можете безпосередньо використовувати класи Dataflow і ControlFlow, надаючи точніший контроль над обробкою графів.

ts
import { ControlFlow, Dataflow } from 'rete-engine'

const control = new ControlFlow(editor)
const dataflow = new Dataflow(editor)

control.add(startNode, {
  inputs: () => [],
  outputs: () => ['exec'],
  async execute(input, forward) {
    const inputs = await dataflow.fetchInputs(startNode.id)

    forward('exec')
  }
})
dataflow.add(startNode, {
  inputs: () => ['data'],
  outputs: () => ['data'],
  data(fetchInputs) {
    const inputs = await fetchInputs()
    const data = {
      data: inputs.data[0] // пересилання вхідних даних (за умови, що існує лише одне вхідне з’єднання з портом "data")
    }

    return data
  }
})

Висновок

Ця версія двигуна містить переглянуті підходи до обробки графіків і усуває недоліки попередньої версії, яка спочатку була орієнтована на жорсткий потік даних без підтримки рекурсії.

Коли мова заходить про обробку графів, немає універсального рішення для всіх. У простих випадках ви можете використовувати DataflowEngine і ControlFlowEngine, а в більш складних випадках ви можете використовувати ControlFlow і Dataflow або написати власне рішення, вивчивши вихідний код пакету rete-engine