Dynamic amount of input-pins in a plugin (like stallone)


#1

hello dear community.

unfortunately i haven´t found anything in the forum regarding this issue.
i wonder whether it is possible to add dynamically inputpins to a dynamic plugin, basically like we can do in the stallone node. and if yes, how can i call the fields?

thank you all
aiv


#2

thats as simple as normal pins:

code(lang=csharp):
[Input(“Input”, IsPinGroup = true)](Input(“Input”, IsPinGroup = true))
ISpread<ISpread> FInput;


#3

But what if you want to add & delete pins from within the plug-in and not through the user editing values with the Inspektor? Are there any examples or do we have to bang our heads against the plug-in specs to find out if there is a way?

For example I would like to create more convenient Cons nodes that auto-magically changes input pin count so there’s always one empty NIL pin to the right so you can just connect stuff without trying to foresee how many inputs you’ll need or constantly have to use the Inspektor to change the input count as your needs change.


#4

to create pins like this have a look at

VVVV.Hosting.Pins.PinFactory.CreatePin(IPluginHost host, PinAttribute attribute)

where attributes is either an Input-, Output- or ConfigAttribute.
you delete those pins via their Dispose method.

mind you, when implementing your example you might run into two issues:

  • it’s not possible to set the slice count of an input pin, as the internal data pointer points to the upstream output pin. though the ISpread interface will let you do it (as it buffers data in a managed array), the change won’t get through to vvvv and in the next frame your slice count will be back at one. you’ll probably have to use the Connected, Disconnected events on your last input pin.
  • adding and deleting pins on the fly, without making use of a config pin, will lead to issues when reopening the patch. config pins are like constants in c#, which can be evaluated at “compile” time so to say. when vvvv creates a node the calling order is like this:
  • create the node

  • node creates bunch of pins

  • call back the node with values from config pins stored in v4p file

  • optional: node creates additional pins based on values from config pins

  • connect the node with other nodes as described by v4p file

if you don’t go the route via a config pin i fear connecting your node will fail.

i tried to write down a first draft, didn’t test this code or anything, just to give you a clue how to address the whole thing. hope it helps!

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using VVVV.Hosting.Pins;
using VVVV.PluginInterfaces.V2;

namespace VVVV.Nodes
{
    public class AdvancedCons<T> : IPluginEvaluate, IPartImportsSatisfiedNotification
    {
        [Config("Input Count", DefaultValue = 2)](Config("Input Count", DefaultValue = 2))
        public IDiffSpread<int> FInputCountConfigPin;
        
        [Import](Import)
        protected IPluginHost2 FPluginHost;

        private readonly List<Pin<T>> FInputPins = new List<Pin<T>>();
        private Pin<T> FLastPin;
        
        public void OnImportsSatisfied()
        {
            FInputCountConfigPin.Changed += FInputCountConfigPing_Changed;
            // Not sure if Changed is raised here after, 
            // otherwise you'll have to raise it yourself
        }
        
        private Pin<T> LastPin
        {
            get
            {
                return FLastPin;
            }
            set
            {
                if (FLastPin != null)
                {
                    FLastPin.Connected -= HandleLastPinConnected;
                    FLastPin.Disconnected -= HandleLastPinDisconnected;
                }
                
                FLastPin = value;
                
                if (FLastPin != null)
                {
                    FLastPin.Connected += HandleLastPinConnected;
                    FLastPin.Disconnected += HandleLastPinDisconnected;
                }
            }
        }

        void HandleInputCountConfigPinChanged(IDiffSpread<int> inputCountConfigPin)
        {
            // Create new pins and set LastPin accordingly
            for (int i = FInputPins.Count; i < inputCountConfigPin.SliceCount; i++)
            {
                Pin<T> pin = PinFactory.CreatePin<T>(FPluginHost, new InputAttribute(string.Format("Input {0}", i)));
                LastPin = pin;
            }
            
            // Delete old pins and set LastPin accordingly
            for (int i = FInputPins.Count - 1; i >= inputCountConfigPin.SliceCount; i--)
            {
                Pin<T> pin = FInputPins[i](i);
                FInputPins.Remove(pin);
                LastPin = FInputPins[FInputPins.Count - 1](FInputPins.Count - 1);
                pin.Dispose();
            }
        }

        void HandleLastPinConnected(object sender, PinConnectionEventArgs args)
        {
            // This should trigger HandleInputCountConfigPinChanged
            FInputCountConfigPin[0](0)++;
        }
        
        void HandleLastPinDisconnected(object sender, PinConnectionEventArgs args)
        {
            // This should trigger HandleInputCountConfigPinChanged
            FInputCountConfigPin[0](0)--;
        }
        
        public void Evaluate(int SpreadMax)
        {
            throw new NotImplementedException();
        }
    }
}

#5

thank you tonfilm.
um… but i cannot do

ISpread<ISpread<ISpread<string>>> FMyFieldName;

right? In a way that autmatically for each FMyFieldName Pin a BinSize-Pin BinSize-Pin is created too?
at least i get exeptions…


#6

right can’t do that, thats not implemented.


#7

i tried something similar, but i get weird exceptions. unfortunately the documentation is not really good for that kind of stuff.

what i eventually want to do is create input and output pins according to a configurable scheme.

can you point me in the right direction?

- region usings
using System;
using System.ComponentModel.Composition;

using VVVV.PluginInterfaces.V1;
using VVVV.PluginInterfaces.V2;
using VVVV.Utils.VColor;
using VVVV.Utils.VMath;

using VVVV.Core.Logging;
- endregion usings

namespace VVVV.Nodes
{
	#region PluginInfo
	[PluginInfo(Name = "Sender", Category = "Event", Help = "Basic template with one value in/out", Tags = "")](PluginInfo(Name = "Sender", Category = "Event", Help = "Basic template with one value in/out", Tags = ""))
	#endregion PluginInfo
	public class EventSenderNode : IPluginEvaluate
	{
		#region fields & pins
		[Input("Create", DefaultValue = 0.0)](Input("Create", DefaultValue = 0.0))
		ISpread<bool> FCreate;



		[Output("Output")](Output("Output"))
		ISpread<double> FOutput;

		[Import()](Import())
		ILogger FLogger;
		IPluginHost2 FHost;
		IPinFactory PinFactory;
		
		#endregion fields & pins

		//called when data for any output pin is requested

		public void Evaluate(int SpreadMax)
		{
			FOutput.SliceCount = SpreadMax;
			
			if (FCreate[0](0)) {
		
				InputAttribute att = new InputAttribute("test");
				att.DefaultValue = 0;
				att.Name = "test";
 				
				Pin<double> pin = PinFactory.CreatePin<double>(att);
				
			}

			for (int i = 0; i < SpreadMax; i++)
				FOutput[i](i) = 2;

			//FLogger.Log(LogType.Debug, "hi tty!");
		}
	}
}

#8

you have to remove the IPinFactory line, import IPluginHost or IPluginHost2 and then call the static class PinFactory.CreatePin(…).


#9

i keep getting the same runtime exception if i use this instead:

edit: put in the recommended change

- region usings
using System;
using System.ComponentModel.Composition;

using VVVV.PluginInterfaces.V1;
using VVVV.PluginInterfaces.V2;
using VVVV.Utils.VColor;
using VVVV.Utils.VMath;

using VVVV.Core.Logging;
- endregion usings

namespace VVVV.Nodes
{
	#region PluginInfo
	[PluginInfo(Name = "Sender", Category = "Event", Help = "Basic template with one value in/out", Tags = "")](PluginInfo(Name = "Sender", Category = "Event", Help = "Basic template with one value in/out", Tags = ""))
	#endregion PluginInfo
	public class EventSenderNode : IPluginEvaluate
	{
		#region fields & pins
		[Input("Create", DefaultValue = 0.0)](Input("Create", DefaultValue = 0.0))
		ISpread<bool> FCreate;



		[Output("Output")](Output("Output"))
		ISpread<double> FOutput;

		[Import()](Import())
		ILogger FLogger;

		[Import()](Import())
		IPluginHost2 FHost;
		 
		
		#endregion fields & pins

		//called when data for any output pin is requested

		public EventSenderNode() {
//				IValueOut pin;
//				FHost.CreateValueOutput("config", 1, new string[]() {"x"}, TSliceMode.Single, TPinVisibility.True, out pin);
		}
		
		public void Evaluate(int SpreadMax)
		{
			FOutput.SliceCount = SpreadMax;
			
			if (FCreate[0](0)) {
				IValueOut pin;
				FHost.CreateValueOutput("test", 1, new string[]() {"x"}, TSliceMode.Dynamic, TPinVisibility.True, out pin);
			}

			for (int i = 0; i < SpreadMax; i++)
				FOutput[i](i) = 2;

			//FLogger.Log(LogType.Debug, "hi tty!");
		}
	}
}

#10

don’t you miss an Import above the IPluginHost2 FHost line?


#11

yes, each field needs its own import attribute, i think thats the only thing you got wrong…


#12

thanks, works nicely.


#13

hi velcrome , it would be nice to see some of your working approaches .


#14

proper documentation will follow, but as of yesterday a new development branch made it into develop where pin creation was refactored. if you use the sdk, pull in latest changes from upstream and have a look at the dynamic plugin Template (Value DynamicPins) to see how to create a stallone like node.


#15

thanks … no text …


#16

i found a little bit of time to continue working on it and quickly hit the next wall.

the current state of the plugin is still basic, it behaves pretty much like a decons but without the magic of PinGroup, because I want the output pins to be named dynamically.

the wall is when i reload the patch- the dynamically produced pins are gone and if i recreate them during the first evaluate, the corrupt links are long deleted. creating them in the constructor gives exceptions and the method SetPluginHost does not work with V2-plugs.

how would i go about this?

status quo (16.3 kB)


#17

to use the host in the constructor, you need to use the ImportingConstructor and Import tag together with the constructor. search the forums, there are some examples.


#18

i tried everything that was listed in a bunch of threads, but still the same exception :(

next version, with [ImportingConstructor] (27.3 kB)


#19

you need to import the host in the constructor as well.


#20

oh, now i found another problem. you cannot read the pin values in the constructor. if you want to define pin names by a pin you have to consider a Config pin and do the pin creation in the config callback.