jagenjo / litegraph.js

A graph node engine and editor written in Javascript similar to PD or UDK Blueprints, comes with its own editor in HTML5 Canvas2D. The engine can run client side or server side using Node. It allows to export graphs as JSONs to be included in applications independently.
MIT License
5.51k stars 623 forks source link

Is it possible to connect two outputs to one input? #36

Closed fredrik-hjarner closed 6 years ago

fredrik-hjarner commented 6 years ago

First I connect an output to an input and that works. Then I connect another output to the same input. The second connection is created but this causes the first connection to break.

Is it possible to connect two outputs to one input?

jagenjo commented 6 years ago

nop, because a node can only read one data from one input slot. Imagine a node with two inputs connected to the same slot, one connection comes with value 5 and the other one with value 9, which data reads the node from that slot?

But you can create nodes that have a variable number of inputs so you can connect as many things as you want to different slots

fredrik-hjarner commented 6 years ago

Thank you for the answer! I see your point. A value of 5 + 9 = 14 is what I would expect. This is how Web Audio API handles many connections to the same input, they are added.

jagenjo commented 6 years ago

but what if they are to strings? you could concatenate them but in which order?, or what if they are arrays? or images? I think the best solution is to create mixing nodes, nodes that do that thing you want to do (concatenate, add, etc)

StefansArya commented 5 years ago

Because AudioNode can be connected from multiple node, I would also need this feature to visualize Web Audio API.

I think we can ignore the order of connected node because we can handle these input as an array. And let the array being handled by the execution function.

I'm not pretty sure how the AudioNode.connect() from the source being handled because I found this repository not long ago, so I will give a simple math example.

For the example of multiplying number:

function MyMultiply()
{
  this.addInput("*", "number", "multiple");
  this.addOutput("=", "number");
}

// Function to call when the node is executed
MyMultiply.prototype.onExecute = function()
{
  // getInputData(0) will return an array because we set the input to 'multiple'
  var inputs = this.getInputData(0);

  if(inputs.length === 0)
    this.setOutputData(0, 0);

  var value = inputs[0];
  for (var i = 1; i < inputs.length; i++) {
    value = value * inputs[i];
  }

  this.setOutputData(0, value);
}
jagenjo commented 5 years ago

you approach seems clean, I like it, but still is lots of code to refactor and it will require a shift in the interaction, and I do not have much time now to undergo that overhaul.

while output slots have an array of links, input slots only have one reference to the incoming link, the first step would be to change that, then create a way to disconnect links by clicking on them (not easy, you need to detect if the mouse is ontop of a curve). If you want to give it a try I will start forking and working on those steps.

On the other hand, if you are interested in audio, there is already a mixer that allows to input many audio signals and blend them using other inputs as gain.

Im sorry I cant help you more now.

StefansArya commented 5 years ago

Thanks!

I think it would be amazing if I use this library to visualize my project. I will help contribute on this repository if I have time :+1:

Caumaker commented 3 years ago

Because AudioNode can be connected from multiple node, I would also need this feature to visualize Web Audio API.

I think we can ignore the order of connected node because we can handle these input as an array. And let the array being handled by the execution function.

I'm not pretty sure how the AudioNode.connect() from the source being handled because I found this repository not long ago, so I will give a simple math example.

For the example of multiplying number:

function MyMultiply()
{
  this.addInput("*", "number", "multiple");
  this.addOutput("=", "number");
}

// Function to call when the node is executed
MyMultiply.prototype.onExecute = function()
{
  // getInputData(0) will return an array because we set the input to 'multiple'
  var inputs = this.getInputData(0);

  if(inputs.length === 0)
    this.setOutputData(0, 0);

  var value = inputs[0];
  for (var i = 1; i < inputs.length; i++) {
    value = value * inputs[i];
  }

  this.setOutputData(0, value);
}

Hi there, I need this behavior too. Let's build it together?

atlasan commented 3 years ago

Hi there, the multiple input is an interesting upgrade, but as Javi said it's quite a big refactoring. In the UE blueprints is possible to connect many inputs to a node when they are the subject to work with, like an object to which apply functions. What is indeed interesting is the possibility to have the many action otputs to be sent to the save input allowing to a more flexible flow. (to simulate this I use the sequencer node) Anyway I think that an easier way would be code a node to have dynamic inputs, so making easy to add new ones, and maybe allowing to add them just releasing a new input on them. From what I know this could be a lot easier than make the whole multi-input system.

StefansArya commented 3 years ago

@Caumaker Hi, sadly my design requirement is also higher than current litegraph.js feature. But I think atlasan idea is more suitable implementation for litegraph.js.

A few month ago I decided to develop Blackprint that could have multiple port connection, data type validation with the actual object reference, and some performance and development optimization (like hot reload). But it's still experimental and I'm still freelancing on other project.

I'm sorry that I haven't contribute to litegraph.js.

jagenjo commented 3 years ago

Litegraph was created as a flow mechanism for data. Two connections in a single input broke the system as when reading from the input what should you read? the first? a blend? an array of N? Blending would be tricky, how you blend arrays? or numbers that could be anything? And having an array meant every single node will have to control both cases (regular data or array data).

The only place where using multiple connections make sense is in Events, but that will require to change lots of code so for now I leave it like this.

Caumaker commented 3 years ago

Yep. I read the source and it appear to be a really heavy task. Thanks for every reply. I choosed another approach to overpass it. I created a button in my interface that appear when the user select the node. I shown this button dynamicly throught onSelected/onDeselected functions. So, when the user clicks on this button, it increase the number of inputs related to that kind of input that he wants. I am using (nuxt.js) with a centralized data store (vuex) in my project. I generated every node dynamicly based on a js object comming from the store. In this object I describ every property and behavior (onExecute) when it runs for example. When the user hits the button It check if the selected node is selected (loop over LGraph._nodes[x].is_selected) and is the right type to recevice that kind of input, so I call (LGraph._nodes[x].addInput('title_input','data_type')). This approach solved the needs to have many connections in one input, and personally has a better look also. Thanks Javi for your great library and work.

gemul commented 6 months ago

But you can create nodes that have a variable number of inputs so you can connect as many things as you want to different slots

Could you explain how to do that? i can't seem to find how to do it in the documentation. Thanks.

clarkmcc commented 5 months ago

@gemul probably some edge cases here, but this is the idea

export class NodeConsoleLog extends BaseNode {
  private counter: number = 0;

  constructor() {
    super();

    this.title = "Console Log";

    this.addInput("message", "string");
    this.addInput(`data${this.counter}`, 0 as any);
  }

  onConnectInput(
    inputIndex: number,
    outputType: string | -1,
    outputSlot: INodeOutputSlot,
    outputNode: LGraphNode,
    outputIndex: number
  ): boolean {
    if (inputIndex >= 1) {
      this.addInput(`data${++this.counter}`, 0 as any);
    }
    return true;
  }

  onConnectionsChange(
    type: number,
    slotIndex: number,
    isConnected: boolean,
    link: LLink,
    ioSlot: INodeInputSlot | INodeOutputSlot
  ): void {
    if (type === LiteGraph.INPUT && slotIndex >= 1) {
      if (!isConnected) {
        this.removeInput(slotIndex);
      }
    }
  }
}
jagenjo commented 5 months ago

But you can create nodes that have a variable number of inputs so you can connect as many things as you want to different slots

Could you explain how to do that? i can't seem to find how to do it in the documentation. Thanks.

you can add a button widget using addWidget and assign it to a function that calls addInput.