Source: core/vvvv.core.js

// VVVV.js -- Visual Web Client Programming
// (c) 2011 Matthias Zauner
// VVVV.js is freely distributable under the MIT license.
// Additional authors of sub components are mentioned at the specific code locations.


VVVV.MousePositions = {'_all': {'x': 0.0, 'y': 0.0, 'wheel': 0.0, 'lb': 0.0, 'mb': 0.0, 'rb': 0.0}}

/**
 * @const
 * @property {Integer} Input 0
 * @property {Integer} Output 1
 * @property {Integer} Configuration 2
 */
var PinDirection = { Input : 0,Output : 1,Configuration : 2 };

/**
 * The default pin type, used if no further specified
 * @memberof VVVV.PinTypes
 * @mixin
 * @property {String} typeName Generic
 * @property {Boolean} reset_on_disconnect true
 * @property {String} defaultValue '0'
 */
VVVV.PinTypes.Generic = {
  typeName: "Generic",
  reset_on_disconnect: true,
  defaultValue: function() { return '0' }
}

/**
 * Value Pin Type
 * @memberof VVVV.PinTypes
 * @mixin
 * @property {String} typeName Value
 * @property {Boolean} reset_on_disconnect false
 * @property {String} defaultValue 0
 * @property {Boolean} primitive true
 */
VVVV.PinTypes.Value = {
  typeName: "Value",
  reset_on_disconnect: false,
  defaultValue: function() { return 0 },
  primitive: true
}

/**
 * String Pin Type
 * @memberof VVVV.PinTypes
 * @mixin
 * @property {String} typeName String
 * @property {Boolean} reset_on_disconnect false
 * @property {String} defaultValue ''
 * @property {Boolean} primitive true
 */
VVVV.PinTypes.String = {
  typeName: "String",
  reset_on_disconnect: false,
  defaultValue: function() { return '' },
  primitive: true
}

/**
 * Enum Pin Type
 * @memberof VVVV.PinTypes
 * @mixin
 * @property {String} typeName Enum
 * @property {Boolean} reset_on_disconnect false
 * @property {String} defaultValue ''
 * @property {Boolean} primitive true
 */
VVVV.PinTypes.Enum = {
  typeName: "Enum",
  reset_on_disconnect: false,
  defaultValue: function() { return '' },
  primitive: true
}

/**
 * Contains various unsorted helper methods
 * @namespace
 */
VVVV.Helpers = {
  
  /**
   * Translates a verbous operator (from the nodename) to a symbol
   * @param {String} l The verbous operator
   * @return {String} the operator symbol
   */
  translateOperators: function(l) {
    l = l.replace("Add", "+");
    l = l.replace("Subtract", "-");
    l = l.replace("Multiply", "*");
    l = l.replace("Divide", "/");
    l = l.replace("EQ", "=");
    l = l.replace("GT", ">");
    l = l.replace("GTE", ">=");
    l = l.replace("LT", "<");
    l = l.replace("LTE", "<=");
    return l;
  },
  
  /**
   * Translates a relative path to an absolute one (usable by the browser) and replaces variables %VVVV% and %PAGE%
   * @param {String} path the relative path
   * @param {VVVV.Core.Patch} patch the patch which the above path is relative to
   * @return {String} the absolute path, usable by the browser
   */
  prepareFilePath: function(path, patch) {
    path = path.replace(/\\/g, '/');
    if (path.match(/^%VVVV%/)) // VVVV.js system path
      return path.replace('%VVVV%', VVVV.Root);
      
    if (path.match(/^%PAGE%/)) // hosting HTML page path
      return path.replace('%PAGE%', location.pathname);
      
    if (path.match(/^(\/|.+:\/\/)/)) // path starting with / or an URL protocol (http://, ftp://, ..)
      return path;
    
    if (patch)  
      return patch.getAbsolutePath()+path;
    else
      return path;
  }, 
  
  /**
   * Helper function that helps creating a dynamic number of input pins
   * see e.g. Add (Value) for usage
   * @param {VVVV.Core.Node} node the node the pins should be added to/removed from
   * @param {Array} pins the array which holds the VVVV.Core.Pin objects; will be modified by the function
   * @param {Integer} count the desired number of pins
   * @param {Function} create_callback the function which actually creates the pin
   */
  dynamicPins: function(node, pins, count, create_callback) {
    var currentCount = pins.length;
    for (var i=currentCount; i<count; i++) {
      pins[i] = create_callback.call(node, i);
    }
    for (var i=count; i<pins.length; i++) {
      node.removeInputPin(pins[i].pinname);
    }
    pins.length = count;
    node.parentPatch.afterUpdate();
  }
}

/** @namespace */
VVVV.Core = {	
  
  /**
   * @class
   * @constructor
   * @param {String} pinname Pin Name
   * @param {String} direction see {@link PinDirection}
   * @param {Array} init_values the array of initial values
   * @param {VVVV.Core.Node} node the node this pin is attached to
   * @param {Object} [type] the PinType, default is {@link VVVV.PinTypes.Generic), see {@link VVVV.PinTypes}
   */
  Pin: function(pinname,direction, init_values, node, type) {
    /** see {@link PinDirection} */
    this.direction = direction;
    /** @member */
    this.pinname = pinname;
    /** @member */
    this.links = [];
    /** @member */
    this.values = [];
    /** @member */
    this.node = node;
    /** @member */
    this.changed = true;
    this.active = false;
    this.reset_on_disconnect = false;
    /** if the pin is a subpatch's input pin, the slavePin is the corresponding IOBox input pin INSIDE the subpatch */ 
    this.slavePin = undefined;
    /** if the pin is a subpatch's output pin, the masterPin is the corresponding IOBox output pin INSIDE the subpatch */ 
    this.masterPin = undefined;
    /** contains a row of named callback functions, each fired if the pin's connection has changed */
    this.connectionChangedHandlers = {};
    /** contains the options used if the pin is of type {@link VVVV.PinTypes.Enum} */
    this.enumOptions = [];
    
    /**
     * retreives pin's slices
     * @param {Integer} i the slice/bin number
     * @param {Integer} [binSize] the bin size, default is 1
     * @return if binSize is 1, the value of the slice is returned; if binSize is > 1, an array with the slice values is returned
     */
    this.getValue = function(i, binSize) {
      if (!binSize || binSize==1)
        return this.values[i%this.values.length];
      var ret = [];
      for (var j=0; j<binSize; j++) {
        ret.push(this.values[(i*binSize+j)%this.values.length]);
      }
      return ret;
    }
    
    /**
     * set a pin's slice value; if an output pin, it also sets the values of connected input pins. If the pin is a subpatch input pin, it also sets the slavePin inside the subpatch
     * @param {Integer} i the slice number
     * @param v the value to set
     * @param {Boolean} [stopPropagation] default is false; if true, the function does not update slavePins to avoid infinite loops; this parameter should not be used in node implementations
     */
    this.setValue = function(i, v, stopPropagation) {
      stopPropagation = stopPropagation || false
      this.values[i] = v;
      this.changed = true;
      this.node.dirty = true;
      var that = this;
      if (this.direction==PinDirection.Output) {
        var linkCount = this.links.length;
        for (var j=0; j<linkCount; j++) {
          this.links[j].toPin.setValue(i, v);
        }
      }
      if (this.slavePin && !stopPropagation) {
        this.slavePin.setValue(i, v);
      }
      if (this.direction==PinDirection.Input && this.masterPin && !this.isConnected())
        this.masterPin.setValue(i, v, true);
        
      if (this.node.isIOBox && this.pinname=='Descriptive Name') {
        if (this.node.parentPatch.domInterface)
          this.node.parentPatch.domInterface.connect(this.node);
        else if (this.node.parentPatch.parentPatch)
          this.node.registerInterfacePin();
      }
    }
    
    /**
     * used to mark a pin as changed without actually using {@link VVVV.Core.Pin#setValue}
     */
    this.markPinAsChanged = function() {
      this.changed = true;
      this.node.dirty = true;
      var that = this;
      if (this.direction==PinDirection.Output) {
        var linkCount = this.links.length;
        for (var i=0; i<linkCount; i++) {
          this.links[i].toPin.markPinAsChanged();
        }
      }
      if (this.slavePin) {
        this.slavePin.markPinAsChanged();
      }
    }
    
    /**
     * used to find out if a pin has changed since last evaluation
     * @return {Boolean} true if changed, false if not changed
     */
    this.pinIsChanged = function() {
      var ret = this.changed;
      this.changed = false;
      return ret;
    }
    
    /**
     * used do find out if a pin is connected
     * @return true, if there are incoming or outgoing links to or from this pin (and its masterPin, if preset)
     */
    this.isConnected = function() {
      return (this.links.length > 0 || (this.masterPin && this.masterPin.isConnected()));
    }
    
    /**
     * @return {Integer} the number of slices
     */
    this.getSliceCount = function() {
      return this.values.length;
    }
	
	  /**
	   * sets the number of slices; also sets the slice number of connected downstream pins and the slavePin if present; absolutely necessary if the slice number decreases
	   * @param {Integer} len the slice count
	   */
    this.setSliceCount = function(len) {
      if (this.values.length==len)
        return;
      this.values.length = len;
      this.changed = true;	  
      this.node.dirty = true;
      if (this.direction==PinDirection.Output) {
  	    var linkCount = this.links.length;
        for (var i=0; i<linkCount; i++) {
          this.links[i].toPin.setSliceCount(len);
        }
      }
      if (this.slavePin) {
        this.slavePin.setSliceCount(len);
      }
    }
    
    /**
     * used to change the pin's type during runtime. Also sets the value to the new pin type's default value
     * @param {Object} newType the new type, see {@link VVVV.PinTypes}
     */
    this.setType = function(newType) {
      if (newType.typeName == this.typeName)
        return;
      var that = this;
      delete this.connectionChangedHandlers['nodepin'];
      delete this.connectionChangedHandlers['webglresource'];
      _(newType.connectionChangedHandlers).each(function(handler, key) {
        that.connectionChangedHandlers[key] = newType.connectionChangedHandlers[key];
      });
      this.typeName = newType.typeName;
      this.defaultValue = newType.defaultValue;
      
      if (this.direction == PinDirection.Input && this.defaultValue) {
        this.setValue(0, this.defaultValue());
        this.setSliceCount(1);
      }
      
      if (newType.reset_on_disconnect!=undefined)
        this.reset_on_disconnect = newType.reset_on_disconnect;
    }
    
    if (type==undefined) {
      type = VVVV.PinTypes.Generic;
      this.unvalidated = true;
    }
    this.setType(type);
    
    if (init_values && init_values.length>0) // override PinType's default value with values from constructor, if it isn't []
      this.values = init_values.slice(0); // use slice(0) to create a copy of the array
    
    this.reset = function() {
      if (this.defaultValue) {
        this.setValue(0, this.defaultValue());
        this.setSliceCount(1);
      }
      else
        this.values = init_values.slice(0);
      this.markPinAsChanged();
    }
    
    /**
     * called when the pin gets connected or disconnected; subsequently calls the callbacks registered in {@link VVVV.Core.Pin#connectionChangedHandlers}
     */
    this.connectionChanged = function() {
      var that = this;
      _(this.connectionChangedHandlers).each(function(handler) {
        that.f = handler;
        that.f();
      });
    }
  },
  
  /**
   * @class
   * @constructor
   * @param {Integer} id the Node ID
   * @param {String} nodename the Node Name
   * @param {VVVV.Core.Patch} [parentPatch] the {@link VVVV.Core.Patch} the node is nested
   */
  Node: function(id, nodename, parentPatch) {
  
    /** the nodename; might be e.g. a name in format NodeName (Category), SomeShader.fx or a path/to/a/subpatch.v4p */
    this.nodename = nodename;
    /** the node ID */
    this.id = id;
    /** X position in pixels inside the parent patch */
    this.x = 0;
    /** Y position in pixels inside the parent patch */
    this.y = 0;
    /** Node width (in a weird unit); use {@link VVVV.Core.Node.getWidth()} for pixel value */
    this.width = 0;
    /** Node width (in a weird unit); use {@link VVVV.Core.Node.getHeight()} for pixel value */
    this.height = 0;
    /** flag indicating whether a node is an IOBox */
    this.isIOBox = false;
    /** flag indicating whether a node is a shader node */
    this.isShader = false;
    /** the number of subsequent resources (subpatches, shaders, 3rd party libs, etc.) that are currently being loaded. Is 0 if nothing is pending */
    this.resourcesPending = 0;
    /** flag indicating if this node should automatically output nil on all output pins, if a nil value is on any input pin */
    this.auto_nil = true;
    
    this.setupObject = function() { // had to put this into a method to allow Patch to "derive" from Node. Really have to understand this javascript prototype thing some day ...
      this.inputPins = {};
      this.outputPins = {};
      this.invisiblePins = {} ;
	
	    this.defaultPinValues = {};
	  };
	  this.setupObject();
    
    /** a flag indicating if this node should evaluate each frame, no matter if it's marked dirty or not */
    this.auto_evaluate = false;
    this.delays_output = false;
    
    /** a flag indicating if any of this node's input pins has changed */
    this.dirty = true;
    
    /** the patch containing this node */
    this.parentPatch = parentPatch;
    if (parentPatch)
      this.parentPatch.nodeMap[id] = this;
	  
	  /**
	   * saves a pin value coming from the patch XML for later use
	   * @param {String} pinname the pin's name
	   * @param {Array} value the array of values (slices)
	   */
    this.addDefault = function(pinname, value) {
      this.defaultPinValues[pinname] = value;
    }
    
    /**
     * Creates a new input pin and adds it to the node. If pin values from the XML have been registered through {@link VVVV.Core.Node.addDefault},
     * these values are assigned
     * @param {String} pinname the new pin's name
     * @param {Array} value the default spread
     * @param {Object} type see {@link VVVV.PinTypes}
     * @return {VVVV.Core.Pin} the new {@link VVVV.Core.Pin}
     */
    this.addInputPin = function(pinname, value, type) {
      var pin = new VVVV.Core.Pin(pinname,PinDirection.Input, value, this, type);
      this.inputPins[pinname] = pin;
      if (this.parentPatch)
        this.parentPatch.pinMap[this.id+'_in_'+pinname] = pin;
      this.applyPinValuesFromXML(pinname);
      return pin;
    }
 
    /**
     * Creates a new output pin and adds it to the node.
     * @param {String} pinname the new pin's name
     * @param {Array} value the default spread
     * @param {Object} type see {@link VVVV.PinTypes}
     * @return {VVVV.Core.Pin} the new {@link VVVV.Core.Pin}
     */
    this.addOutputPin = function(pinname, value, type) {
      var pin = new VVVV.Core.Pin(pinname,PinDirection.Output, value, this, type);
      this.outputPins[pinname] = pin;
      if (this.parentPatch)
        this.parentPatch.pinMap[this.id+'_out_'+pinname] = pin;
      return pin;
    }
    
    /**
     * deletes an input pin and all incoming links
     * @param pinname the name of the pin to delete
     */
    this.removeInputPin = function(pinname) {
      if (!this.inputPins[pinname]) return;
      var l = this.inputPins[pinname].links[0];
      if (l) {
        l.fromPin.connectionChanged();
        l.destroy();
      }
      delete this.inputPins[pinname];
      this.dirty = true;
    }
    
    /**
     * deletes an output pin and all outgoing links
     * @param pinname the name of the pin to delete
     */
    this.removeOutputPin = function(pinname) {
      if (!this.outputPins[pinname]) return;
      var n = this.outputPins[pinname].links.length;
      for (var i=0; i<n; i++) {
        var l = this.outputPins[pinname].links[i];
        l.toPin.connectionChanged();
        l.destroy();
      }
      delete this.outputPins[pinname];
      this.dirty = true;
    }
    
    /**
     * Creates a new invisible/config pin and adds it to the node. If pin values from the XML have been registered through {@link VVVV.Core.Node.addDefault},
     * these values are assigned
     * @param {String} pinname the new pin's name
     * @param {Array} value the default spread
     * @param {Object} type see {@link VVVV.PinTypes}
     * @return {VVVV.Core.Pin} the new {@link VVVV.Core.Pin}
     */
    this.addInvisiblePin = function(pinname, value, type) {
      var pin = new VVVV.Core.Pin(pinname,PinDirection.Configuration, value, this, type);
      this.invisiblePins[pinname] = pin;
      this.parentPatch.pinMap[this.id+'_inv_'+pinname] = pin;
      if (this.defaultPinValues[pinname] != undefined) {
        pin.values = this.defaultPinValues[pinname];
      }
      return pin;
    }
	  
	  /**
	   * Helper to get the type of IOBox (e.g. Value Advanced, String, Color)
	   * @return {String} the type of IOBox
	   */  
    this.IOBoxType = function() {
      var match = /^IOBox \((.*)\)/.exec(this.nodename);
      if (match && match.length>1)
        return match[1];
      return "";
    }
    
    /**
     * Returns the input pin of the IOBox which is represented by the IOBox label
     * @return {VVVV.Core.Pin} the pin represented by the IOBox label, see {@link VVVV.Core.Pin}
     */
    this.IOBoxInputPin = function() {
      switch (this.IOBoxType()) {
        case "Value Advanced":
          return this.inputPins["Y Input Value"];
        case "String": 
          return this.inputPins["Input String"];
        case "Color": 
          return this.inputPins["Color Input"];
        case "Node":
          return this.inputPins["Input Node"];
      }
      return undefined;
    }
    
    /**
     * Returns the output pin of the IOBox which is represented by the IOBox label
     * @return {VVVV.Core.Pin} the pin represented by the IOBox label, see {@link VVVV.Core.Pin}
     */
    this.IOBoxOutputPin = function() {
      switch (this.IOBoxType()) {
        case "Value Advanced":
          return this.outputPins["Y Output Value"];
        case "String": 
          return this.outputPins["Output String"];
        case "Color": 
          return this.outputPins["Color Output"];
        case "Node":
          return this.outputPins["Output Node"];
      }
      return undefined;
    }
    
    /**
     * Returns the number of visible rows of an IOBox. This is basically a convenience method for getting the value of the "Rows" pin 
     * @return {Integer} the number of visible rows
     */
    this.IOBoxRows = function() {
		if (this.invisiblePins["Rows"])
			return this.invisiblePins["Rows"].getValue(0);
		else
			return 1;
    }
    
    /**
     * Tells, if a node is a comment node. Reverse engineering revealed that this is the case, if a String IOBox has no output
     * pins. Maybe better ask someone who actually knows.
     * @return {Boolean} true, if the node is a comment, false otherwise.
     */
    this.isComment = function() {
      return this.isIOBox && _(this.outputPins).size()==0
    }
    
    /**
     * Returns the text shown inside a node box in the editor. In case of an IOBox this is the result of {@link VVVV.Core.Node.IOBoxInputPin};
     * in case of a subpatch this is "|| SubPatchName" (the .v4p extension stripped); in case of a normal node, this is the node name.
     * @return {String} the node's representative label
     */
    this.label = function() {
      if (this.isIOBox) {
        if (this.IOBoxInputPin().getValue(0))
          return this.IOBoxInputPin().getValue(0).toString();
        return '';
      }
      
      if (this.isSubpatch) {
        return "||"+this.nodename.match(/([^\/]+)\.v4p$/)[1];
      }
      
      var label = this.nodename.replace(/\s\(.+\)/, '');
      var label = VVVV.Helpers.translateOperators(label);
      return label;
    }
    
    /**
     * Returns the node with in pixels, used for displaying the patch
     * @return {Integer} the node width in pixels
     */
    this.getWidth = function() {
      var ret;
      if (this.width==100 || this.width==0) {
        if (this.isIOBox)
          ret = 60;
        else
          ret = Math.max(18, (this.label().length+2)*6);
      }
      else
        ret = this.width/15;
      ret = Math.max(ret, (_(this.inputPins).size()-1)*12+4);
      return ret;
    }
    
    /**
     * Returns the node height in pixels, used for displaying the patch
     * @return {Integer} the node height in pixels
     */
    this.getHeight = function() {
      if (this.isIOBox && this.height==100)
        return 18 * this.IOBoxRows();
      if (this.height==100 || this.isSubpatch)
        return 18;
      else
        return Math.max(18, this.height/15);
    }
    
    /**
     * Returns all nodes which are connected to a node's input pins
     * @return {Array} an Array of {@link VVVV.Core.Node} objects
     */
    this.getUpstreamNodes = function() {
      var ret = [];
      _(this.inputPins).each(function(p) {
        if (p.links.length>0)
          ret.push(p.links[0].fromPin.node);
      });
      return ret;
    }
    
    /**
     * Returns all nodes which are connected to a node's output pins
     * @return {Array} an Array of {@link VVVV.Core.Node} objects
     */
    this.getDownstreamNodes = function() {
      var ret = [];
      _(this.outputPins).each(function(p) {
        for (var j=0; j<p.links.length; j++) {
          ret.push(p.links[j].toPin.node);
        }
      });
      return ret;
    }
    
    /**
     * Finds all nodes with a certain name, the node's data eventually flows into.
     * @param {String} name the name of the node to search for
     * @result {Array} an Array of {@link VVVV.Core.Node} objects matching the search
     */
    this.findDownstreamNodes = function(name) {
      var ret = [];
    	_(this.outputPins).each(function(p) {
        for (var j=0; j<p.links.length; j++) {
          if (p.links[j].toPin.node.nodename==name)
            ret.push(p.links[j].toPin.node);
          else {
            if (p.links[j].toPin.slavePin) {
              // enter subpatch
              ret = ret.concat(p.links[j].toPin.slavePin.node.findDownstreamNodes(name));
            }
            else if (p.links[j].toPin.node.isIOBox && p.links[j].toPin.node.IOBoxOutputPin().slavePin) {
              // leave subpatch
              ret = ret.concat(p.links[j].toPin.node.IOBoxOutputPin().slavePin.node.findDownstreamNodes(name));
            }
            else
              ret = ret.concat(p.links[j].toPin.node.findDownstreamNodes(name));
          }
    	  }
    	});
    	return ret;
    }
    
    /**
     * Tells, if a node has any nil inputs
     * @return true, if any of the input pins are true, false otherwise
     */
    this.hasNilInputs = function() {
      var result = false
      _(this.inputPins).each(function(p) {
        if (p.getSliceCount()==0)
          result = true;
      });
      return result;
    }
    
    /**
     * Returns the maximum number of slices of a node's input pins
     * @return the maximum number of slices
     */
    this.getMaxInputSliceCount = function() {
      var ret = 0;
      _(this.inputPins).each(function(p) {
        if (p.getSliceCount()>ret)
          ret = p.values.length;
      });
      return ret;
    }
    
    /**
     * Applies values from the patch XML to an input pin, if present
     * @param {String} pinname the name of the pin
     */
    this.applyPinValuesFromXML = function(pinname) {
      if (!this.inputPins[pinname])
        return;
      var pin = this.inputPins[pinname];
      var values = this.defaultPinValues[pinname];
      if (values != undefined) {
        // this checks for the case when complex input pins have a value of "||" when not connected.
        // this should not override the default value set by the node with ""
        if (!pin.reset_on_disconnect || values.length>1 || values[0]!="") {
          for (var i=0; i<values.length; i++) {
            if (pin.values[i]!=values[i])
              pin.setValue(i, values[i]);
          }
          pin.setSliceCount(values.length);
        }
      }
    }
    
    /**
     * Called, if an IOBox's Descriptive Name inside a subpatch changes, this method creates and updates the subpatch's in and output
     * pins. Subsequently triggers connection changed events for the IOBox's input and output pins.
     */
    this.registerInterfacePin = function() {
      var that = this;
      if (this.isIOBox) {
        if (this.parentPatch.parentPatch && this.invisiblePins["Descriptive Name"].getValue(0)!="") {
          var pinname = this.invisiblePins["Descriptive Name"].getValue(0);
          this.IOBoxInputPin().connectionChangedHandlers['subpatchpins'] = function() {
            if (this.links.length>0 && this.masterPin) {
               if (VVVV_ENV=='development') console.log('deleting '+pinname+' input pin because node has input connection...');
               for (var i=0; i<this.masterPin.links.length; i++) {
                 this.masterPin.links[i].destroy();
               }
               that.parentPatch.removeInputPin(pinname);
               this.masterPin = undefined;
            }
            if (that.IOBoxOutputPin().links.length==0) {
              if (!that.IOBoxOutputPin().slavePin) {
                if (VVVV_ENV=='development') console.log('interfacing output pin detected: '+pinname);
                var pin = that.parentPatch.outputPins[pinname];
                if (pin==undefined)
                  var pin = that.parentPatch.addOutputPin(pinname, that.IOBoxOutputPin().values);
                  
                pin.setType(VVVV.PinTypes[that.IOBoxOutputPin().typeName]);
                  
                that.IOBoxOutputPin().slavePin = pin;
                pin.masterPin = that.IOBoxOutputPin();
              }
              else if (that.IOBoxOutputPin().slavePin.pinname!=pinname) { // rename subpatch pin
                if (VVVV_ENV=='development') console.log('renaming '+that.IOBoxOutputPin().slavePin.pinname+" to "+pinname);
                that.parentPatch.outputPins[pinname] = that.parentPatch.outputPins[that.IOBoxOutputPin().slavePin.pinname];
                that.parentPatch.removeOutputPin(that.IOBoxOutputPin().slavePin.pinname);
                that.IOBoxOutputPin().slavePin.pinname = pinname;
              }
            }
            this.node.parentPatch.parentPatch.afterUpdate();
          }
          this.IOBoxInputPin().connectionChanged();
          
          this.IOBoxOutputPin().connectionChangedHandlers['subpatchpins'] = function() {
            if (this.links.length>0 && this.slavePin) {
               if (VVVV_ENV=='development') console.log('deleting '+pinname+' output pin because node '+that.id+' has output connection...');
               for (var i=0; i<this.slavePin.links.length; i++) {
                 this.slavePin.links[i].destroy();
               }
               that.parentPatch.removeOutputPin(pinname);
               this.slavePin = undefined;
            }
            if (that.IOBoxInputPin().links.length==0) {
              if (!that.IOBoxInputPin().masterPin) {
                if (VVVV_ENV=='development') console.log('interfacing input pin detected: '+pinname);
                var pin = that.parentPatch.inputPins[pinname];
                if (pin==undefined) {
                  if (VVVV_ENV=='development') console.log('creating new input pin at parent patch, using IOBox values');
                  var pin = that.parentPatch.addInputPin(pinname, that.IOBoxInputPin().values);
                }
                else {
                  slicecount = pin.getSliceCount();
                  for (var i=0; i<slicecount; i++) {
                    that.IOBoxInputPin().setValue(i, pin.getValue(i));
                  }
                  if (slicecount>0)
                    that.IOBoxInputPin().setSliceCount(pin.getSliceCount());
                }
                
                var savedValues = pin.values.slice();
                pin.setType(VVVV.PinTypes[that.IOBoxInputPin().typeName]);
                if ((pin.unvalidated && VVVV.PinTypes[pin.typeName].primitive) || pin.isConnected()) {
                  pin.values = savedValues;
                  pin.unvalidated = false;
                }
                
                pin.slavePin = that.IOBoxInputPin();
                that.IOBoxInputPin().masterPin = pin;
              }
              else if (that.IOBoxInputPin().masterPin.pinname!=pinname) { // rename subpatch pin
                console.log('renaming '+that.IOBoxInputPin().masterPin.pinname+" to "+pinname);
                that.parentPatch.inputPins[pinname] = that.parentPatch.inputPins[that.IOBoxInputPin().masterPin.pinname];
                that.parentPatch.removeInputPin(that.IOBoxInputPin().masterPin.pinname);
                that.IOBoxInputPin().masterPin.pinname = pinname;
              }
            }
            this.node.parentPatch.parentPatch.afterUpdate();
          }
          this.IOBoxOutputPin().connectionChanged();
        }
      }
    }
    
    /**
     * Method called immediatly after node creation for setting up common node settings
     */
    this.setup = function() 
    {
      //Add descriptive name for all nodes
      this.addInvisiblePin("Descriptive Name",[""], VVVV.PinTypes.String);
    }
	  
	  /**
	   * Method called AFTER a node's pins have been created and populated with values from patch XML, and BEFORE node links are created.
	   * This method should be overwritten by any Node implementation and is useful for e.g. creating dynamic number of input pins and
	   * other initialising code which should run before first call of {@link VVVV.Core.Node.evaluate}.
	   * @abstract
	   */ 
    this.initialize = function() {
		
    }
	  
	  /**
	   * Method called each frame, if a node is marked dirty or {@link VVVV.Core.Node.auto_evaluate} is true. This method should
	   * be overwritten by any Node implementation and usually holds the node's main logic.
	   * @abstract
	   */ 
    this.evaluate = function() {
      var that = this;
      _(this.outputPins).each(function(p) {
        p.setValue(0, "not calculated");
      });
    }
    
    /**
     * Method called when a node is being deleted. Should be overwritten by any Node implementation to free resources and gracefully
     * shut itself down
     * @abstract
     */
    this.destroy = function() {
      if (this.isIOBox) {
        if (this.IOBoxInputPin().masterPin) {
          this.parentPatch.removeInputPin(this.IOBoxInputPin().masterPin.pinname);
          this.parentPatch.parentPatch.afterUpdate();
        }
        if (this.IOBoxOutputPin().slavePin) {
          this.parentPatch.removeOutputPin(this.IOBoxOutputPin().slavePin.pinname);
          this.parentPatch.parentPatch.afterUpdate();
        }
      }
    }
    
    /**
     * Creates the XML code representing the node and its pins. Called by {@link VVVV.Core.Patch.toXML} on serializing a patch and
     * directly by the editor when nodes are being copied to clipboard
     * @return {String} the node's XML code
     */
    this.serialize = function() {
      var $node = $("<NODE>");
      $node.attr("id", this.id);
      $node.attr("nodename", this.nodename);
      $node.attr("systemname", this.nodename);
      if (this.shaderFile) {
        $node.attr("filename", this.shaderFile.replace(".vvvvjs.fx", ".fx").replace("%VVVV%/effects", "%VVVV%/lib/nodes/effects"));
      }
      if (this.isSubpatch) {
        $node.attr("filename", this.nodename);
        $node.attr("systemname", this.nodename.match("(.*)\.v4p$")[1])
      }
      if (this.isIOBox)
        $node.attr("componentmode", "InABox");
      else
        $node.attr("componentmode", "Hidden");
      
      var $bounds = $("<BOUNDS>");
      if (this.isIOBox)
        $bounds.attr("type", "Box");
      else
        $bounds.attr("type", "Node");
      $bounds.attr("left", parseInt(this.x * 15));
      $bounds.attr("top", parseInt(this.y * 15));
      $bounds.attr("width", parseInt(this.width));
      $bounds.attr("height", parseInt(this.height));
      $node.append($bounds);
      
      var that = this;
      
      _(this.inputPins).each(function(p) {
        var $pin = $("<PIN>");
        $pin.attr("pinname", p.pinname);
        $pin.attr("visible", "1");
        if ((!p.isConnected() || p.masterPin) && VVVV.PinTypes[p.typeName].primitive && that.defaultPinValues[p.pinname]) {
          $pin.attr("values", _(that.defaultPinValues[p.pinname]).map(function(v) { return "|"+v.toString().replace(/\|/g, "||")+"|"; }).join(","));
        }
        $node.append($pin);
      })
      
      _(this.invisiblePins).each(function(p) {
        var $pin = $("<PIN>");
        $pin.attr("pinname", p.pinname);
        $pin.attr("visible", "0");
        if (VVVV.PinTypes[p.typeName].primitive) {
          $pin.attr("values", _(p.values).map(function(v) { return "|"+v.toString().replace(/\|/g, "||")+"|"; }).join(","));
        }
        $node.append($pin);
      })
      
      return $node;
    }

  },
  
  /**
   * @class
   * @constructor
   * @param {VVVV.Core.Pin} fromPin the output pin which is the source of the connection
   * @param {VVVV.Core.Pin} toPin the input pin which is the destination of the connection
   */
  Link: function(fromPin, toPin) {
    this.fromPin = fromPin;
    this.toPin = toPin;
    
    this.fromPin.links.push(this);
    this.toPin.links.push(this);
    
    /**
     * deletes resources associated with a link
     */
    this.destroy = function() {
      if (this.toPin.reset_on_disconnect)
        this.toPin.reset();
      else
        this.toPin.node.defaultPinValues[this.toPin.pinname] = this.toPin.values.slice(0);
      this.fromPin.links.splice(this.fromPin.links.indexOf(this), 1);
      this.toPin.links.splice(this.toPin.links.indexOf(this), 1);
      this.fromPin.node.parentPatch.linkList.splice(this.fromPin.node.parentPatch.linkList.indexOf(this),1);
    }
    
    /**
     * Returns the XML string representing the link. Used for saving the patch and copying to clipboard
     */
    this.serialize = function() {
      // calling it LONK instead of LINK here, because jquery does not make a closing tag for LINK elements
      // renaming it to LINK later ...
      var $link = $("<LONK>");
      $link.attr("srcnodeid", this.fromPin.node.id);
      $link.attr("srcpinname", this.fromPin.pinname);
      $link.attr("dstnodeid", this.toPin.node.id);
      $link.attr("dstpinname", this.toPin.pinname);
      return $link;
    }
  },

  /**
   * @class
   * @constructor
   * @param {String} ressource either a path/to/some/patch.v4p or VVVV XML code
   * @param {Function} success_handler called after the patch (and all sub components) has completely loaded and is ready
   * @param {Function} error_handler called if an error occured, most likey because the .v4p file was not found
   * @param {VVVV.Core.Patch} [parentPatch] the parent patch, if it's a subpatch
   * @param {Integer} id the patch's ID in the parent patch, if it's a subpatch 
   */
  Patch: function(ressource, success_handler, error_handler, parentPatch, id) {
    
    this.ressource = ressource;
    this.vvvv_version = "45_26.1";
    /** the diameter of the patch / the maximum X and Y coordinates of all nodes in a patch */
    this.boundingBox = {width: 0, height: 0};
    /** @member*/
    this.windowWidth = 500;
    /** @member */
    this.windowHeight = 500;
    
    /** a hash table containing the pins of all nodes inside a patch, indexed with [node_id]_[in|out|inv]_[pinname] */
    this.pinMap = {};
    /** a hash table containing all nodes inside a patch, indexed with the node ID */
    this.nodeMap = {};
    /** an array containing all nodes inside a patch */
    this.nodeList = [];
    /** an array containing all links inside a patch */
    this.linkList = [];
    
    /** The {@link VVVV.MainLoop} Object running this patch */
    this.mainloop = undefined;
    
    this.success = success_handler;
    this.error = error_handler;
    
    this.editor = undefined;
    
    this.setupObject();
    
    if (parentPatch)
      this.parentPatch = parentPatch;
    if (id)
      this.id = id;
    
    var print_timing = false;
    
    /**
     * Returns the patch's absolute path, usable for the browser
     * @return {String} the absolute path
     */
    this.getAbsolutePath = function() {
      var path = this.getRelativePath();
      if (this.parentPatch)
        path = this.parentPatch.getAbsolutePath()+path;
      return path;
    }
    
    /**
     * Returns a patch's relative path, as it is specified in the paret patch
     * @return {String} the patch's path relative to its parent patch
     */
    this.getRelativePath = function() {
      var match = this.nodename.match(/(.*\/)?[^/]+\.v4p/);
      return match[1] || '';
    }
    
    /**
     * Called when a patch is deleted. Deletes all containing nodes.
     */
    this.destroy = function() {
      for (var i=0; i<this.nodeList.length; i++) {
        this.nodeList[i].destroy();
        delete this.nodeMap[this.nodeList[i].id];
        delete this.nodeList[i];
      }
    }
    
    /**
     * Creates an array of slices out of pin value string coming from a patch XML
     * @param {String} v the pin value string from the XML
     */
    function splitValues(v) {
      if (v==undefined)
        return [];
      if (this.vvvv_version<="45_26") { // legacy code
        if (/\|/.test(v))
          separator = "|";
        else
          separator = ",";
        return v.split(separator).filter(function(d,i) { return d!=""});
      }
      
      var result = [];
      var currSlice = '';
      var insideValue = false;
      var len = v.length;
      for (var i=0; i<len; i++) {
        if (v[i]==',' && !insideValue) {
          result.push(currSlice);
          currSlice = '';
        }
        else if (v[i]=='|') {
          if (v[i+1]!='|' || i+1==v.length-1)
            insideValue = !insideValue;
          else
            currSlice += v[++i];
        }
        else
          currSlice += v[i];
      }
      result.push(currSlice);
      return result;
    }
    
    if (this.vvvv_version<="45_26") {
      var oldLinks = {};
      var newLinks = {};
      var oldNodes = {};
      var newNodes = {};
    }
    
    var thisPatch = this;
    
    /**
     * Takes a patch XML string, parses it, and applies it to the patch. This method is called once by the constructor, passing the complete patch code, and
     * frequently by an editor, passing in XML snippets. This is the only method you should use to manipulate a patch.
     * @param {String} xml VVVV Patch XML
     * @param {Function} ready_callback called after the XML code has been completely processed, and the patch is fully loaded and ready again
     */
    this.doLoad = function(xml, ready_callback) {
      var p = this;
      do {
        p.dirty = true;
      }
      while (p=p.parentPatch);
      
      var version_match = /^<!DOCTYPE\s+PATCH\s+SYSTEM\s+"(.+)\\(.+)\.dtd/.exec(xml);
      if (version_match)
        thisPatch.vvvv_version = version_match[2].replace(/[a-zA-Z]+/, '_');
      
      // this is kind of a hacky way to determine, if the incoming XML is the complete patch, or a patch change
      var syncmode = 'diff';
      if (/\s<PATCH/.test(xml) || thisPatch.vvvv_version<="45_26") {
        syncmode = 'complete';
        if (VVVV_ENV=='development') console.log('complete: '+this.nodename);
      }
    
      var $windowBounds = $(xml).find('bounds[type="Window"]').first();
      if ($windowBounds.length>0) {
        thisPatch.windowWidth = $windowBounds.attr('width')/15;
        thisPatch.windowHeight = $windowBounds.attr('height')/15;
      }
      
      if (syncmode=='complete')
        newNodes = {};
        
      var nodesLoading = 0;

      $(xml).find('node').each(function() {
        
        // in case of renaming a node, delete the old one first
        if ($(this).attr('createme')=='pronto' && thisPatch.nodeMap[$(this).attr('id')]!=undefined) {
          var n = thisPatch.nodeMap[$(this).attr('id')];
          if (VVVV_ENV=='development') console.log("node renamed, so deleting node "+n.id+' / '+n.nodename);
          
          _(n.inputPins).each(function(p) {
            _(p.links).each(function (link) {
              link.destroy();
              link.fromPin.connectionChanged();
            });
          })
          
          _(n.outputPins).each(function(p) {
            _(p.links).each(function (link) {
              link.destroy();
              link.toPin.connectionChanged();
              link.toPin.markPinAsChanged();
            });
          })
          
          thisPatch.nodeList.splice(thisPatch.nodeList.indexOf(n),1);
          delete thisPatch.nodeMap[n.id];
        }
        
        var $bounds;
        if ($(this).attr('componentmode')=="InABox")
          $bounds = $(this).find('bounds[type="Box"]').first();
        else
          $bounds = $(this).find('bounds[type="Node"]').first();
        
        var nodeExists = thisPatch.nodeMap[$(this).attr('id')]!=undefined;
        if (!nodeExists) {
          var nodename = $(this).attr('systemname')!="" ? $(this).attr('systemname') : $(this).attr('nodename');
          if (nodename==undefined)
            return;       
          if (VVVV.NodeLibrary[nodename.toLowerCase()]!=undefined) {
            var n = new VVVV.NodeLibrary[nodename.toLowerCase()]($(this).attr('id'), thisPatch);
            
            // load 3rd party libs, if required for this node
            if (VVVV.NodeLibrary[nodename.toLowerCase()].requirements) {
              thisPatch.resourcesPending++; // pause patch evaluation
              _(VVVV.NodeLibrary[nodename.toLowerCase()].requirements).each(function(libname) {
                if (VVVV.LoadedLibs[libname]===undefined)
                  VVVV.loadScript(VVVV.ThirdPartyLibs[libname], function() {
                    thisPatch.resourcesPending--; // resume patch evaluation
                  });
              });
            }
          }
          else if (/.fx$/.test($(this).attr('filename'))) {
            var n = new VVVV.Nodes.GenericShader($(this).attr('id'), thisPatch);
            n.isShader = true;
            n.shaderFile = $(this).attr('filename').replace(/\\/g, '/').replace(/\.fx$/, '.vvvvjs.fx').replace('lib/nodes/', '');
            n.nodename = nodename;
          }
          else {
            if (/.v4p$/.test($(this).attr('filename'))) {
              thisPatch.resourcesPending++;
              var that = this;
              var n = new VVVV.Core.Patch($(this).attr('filename'),
                function() {
                  thisPatch.resourcesPending--;
                  if (VVVV_ENV=='development') console.log(this.nodename+'invoking update links')
                  updateLinks(xml);
                  if (thisPatch.editor)
                    thisPatch.editor.addPatch(this);
                  if (this.auto_evaluate) {
                    var p = thisPatch;
                    do {
                      p.auto_evaluate = true;
                    }
                    while (p = p.parentPatch);
                  }
                  this.setMainloop(thisPatch.mainloop);
                  thisPatch.afterUpdate();
                  if (thisPatch.resourcesPending<=0 && ready_callback) {
                    ready_callback();
                    ready_callback = undefined;
                  }
                },
                function() {
                  thisPatch.resourcesPending--;
                  this.not_implemented = true;
                  VVVV.onNotImplemented(nodename);
                  updateLinks(xml);
                  thisPatch.afterUpdate();
                  if (thisPatch.resourcesPending<=0 && ready_callback) {
                    ready_callback();
                    ready_callback = undefined;
                  }
                },
                thisPatch, $(that).attr('id')
              );
              n.isSubpatch = true;
              if (thisPatch.editor && !n.editor)
                thisPatch.editor.addPatch(n);
              thisPatch.nodeMap[n.id] = n;
            }
            else {
              var n = new VVVV.Core.Node($(this).attr('id'), nodename, thisPatch);
              n.not_implemented = true;
              if (syncmode=='diff' && VVVV.Config.auto_undo == true)
                thisPatch.editor.sendUndo();
              VVVV.onNotImplemented(nodename);
            }
          }
          if (VVVV_ENV=='development' && syncmode!='complete') console.log(thisPatch.nodename+': inserted new node '+n.nodename);
        }
        else
          n = thisPatch.nodeMap[$(this).attr('id')];
          
        if (n.auto_evaluate) { // as soon as the patch contains a single auto-evaluate node, it is also an auto evaluating subpatch
          var p = thisPatch;
          do {
            p.auto_evaluate = true;
          }
          while (p = p.parentPatch);
        }
          
        if ($(this).attr('deleteme')=='pronto') {
          if (VVVV_ENV=='development') console.log('removing node '+n.id);
          if (n.isSubpatch) {
            if (n.editor) n.editor.removePatch(n);
            var subpatches = n.getSubPatches();
            subpatches.push(n);
            var path;
            for (var i=0; i<subpatches.length; i++) {
              path = VVVV.Helpers.prepareFilePath(subpatches[i].nodename, subpatches[i].parentPatch);
              VVVV.Patches[path].splice(VVVV.Patches[path].indexOf(n), 1);
              if (VVVV.Patches[path].length == 0)
                delete VVVV.Patches[path];
            }
          }
          thisPatch.nodeList.splice(thisPatch.nodeList.indexOf(n),1);
          n.destroy();
          delete thisPatch.nodeMap[n.id];
        }
        
        if ($bounds.length>0) {
          if ($bounds.attr('left')) {
            n.x = $bounds.attr('left')/15;
            n.y = $bounds.attr('top')/15;
            thisPatch.boundingBox.width = Math.max(thisPatch.boundingBox.width, n.x+100);
            thisPatch.boundingBox.height = Math.max(thisPatch.boundingBox.height, n.y+100);
          }
          if ($bounds.attr('width')) {
            n.width = $bounds.attr('width');
            n.height = $bounds.attr('height');
          }
        }
        
        if (/^iobox/.test(n.nodename.toLowerCase()))
          n.isIOBox = true;
		  
        //To add anything which relates to all nodes
        if (!nodeExists)
          n.setup();
        
        var that = this;

        // PINS
        $(this).find('pin').each(function() {
          var pinname = $(this).attr('pinname');
          var values = splitValues($(this).attr('values'));
		  
          //Get all defaults from xml
          if (values!=undefined) {
            if (values.length > 0)
              n.addDefault(pinname, values);
          }
          
          // if the output pin already exists (because the node created it), skip
          if (n.outputPins[pinname]!=undefined)
            return;
            
          // the input pin already exists (because the node created it), don't add it, but set values, if present in the xml
          if (n.inputPins[pinname]!=undefined) {
            if (!n.inputPins[pinname].isConnected()) {
              n.applyPinValuesFromXML(pinname);
            }
            return;
          }
          
          // the input pin already exists (because the node created it), don't add it, but set values, if present in the xml
          if (n.invisiblePins[pinname]!=undefined) {
            if (values!=undefined) {
              for (var i=0; i<values.length; i++) {
                if (n.invisiblePins[pinname].values[i]!=values[i])
                  n.invisiblePins[pinname].setValue(i, values[i]);
              }
              n.invisiblePins[pinname].setSliceCount(values.length);
            }
            return;
          }
  		    
          //Check for non implemented nodes
          if (($(this).attr('visible')==1 && $(this).attr('pintype')!='Configuration') || n.isSubpatch) {
            if ($(this).attr('pintype')=="Output" || $(xml).find('link[srcnodeid='+n.id+']').filter("link[srcpinname='"+pinname.replace(/[\[\]]/,'')+"']").length > 0) {
              if (n.outputPins[pinname] == undefined) {
                //Add as output list if not already there
                n.addOutputPin(pinname, values);
              }
            }
            else {
              if (n.inputPins[pinname] == undefined && n.invisiblePins[pinname] == undefined) {
                //Add as intput is neither in invisible/input list
                n.addInputPin(pinname, values);
              }
            }
          }
          else {
            if (n.inputPins[pinname] == undefined && n.invisiblePins[pinname] == undefined) {
              //Add as invisible pin
              n.addInvisiblePin(pinname, values);
            }
          }
              
        });
        
        //Initialize node
        if (!nodeExists) {
          n.initialize();
          thisPatch.nodeList.push(n);
        }
        
        if (syncmode=='complete')
          newNodes[n.id] = n;
        
      });
      
      if (syncmode=='complete') {
        _(oldNodes).each(function(n, id) {
          if (newNodes[id]==undefined) {
            if (VVVV_ENV=='development') console.log('removing node '+n.id);
            thisPatch.nodeList.splice(thisPatch.nodeList.indexOf(n),1);
            delete thisPatch.nodeMap[n.id];
          }
        });
        oldNodes = {};
        _(newNodes).each(function(n, id) {
          oldNodes[id] = n;
        });
      }
    
      if (this.resourcesPending===0)
        updateLinks(xml);
        
      function updateLinks(xml) {
        if (syncmode=='complete')
          newLinks = {};
        
        // first delete marked links 
        $(xml).find('link[deleteme="pronto"]').each(function() {
          var link = false;
          for (var i=0; i<thisPatch.linkList.length; i++) {
            if (thisPatch.linkList[i].fromPin.node.id==$(this).attr('srcnodeid') &&
                thisPatch.linkList[i].fromPin.pinname==$(this).attr('srcpinname') &&
                thisPatch.linkList[i].toPin.node.id==$(this).attr('dstnodeid') &&
                thisPatch.linkList[i].toPin.pinname==$(this).attr('dstpinname')) {
              link = thisPatch.linkList[i];
            }
          }
          if (!link)
            return;
          if (VVVV_ENV=='development') console.log('removing '+link.fromPin.pinname+' -> '+link.toPin.pinname);
          var fromPin = link.fromPin;
          var toPin = link.toPin;
          link.destroy();
          fromPin.connectionChanged();
          toPin.connectionChanged();
          toPin.markPinAsChanged();
        });
        
        $(xml).find('link[deleteme!="pronto"]').each(function() {
          var srcPin = thisPatch.pinMap[$(this).attr('srcnodeid')+'_out_'+$(this).attr('srcpinname')];
          var dstPin = thisPatch.pinMap[$(this).attr('dstnodeid')+'_in_'+$(this).attr('dstpinname')];
          
  				// add pins which are neither defined in the node, nor defined in the xml, but only appeare in the links (this is the case with shaders)
          if (srcPin==undefined && thisPatch.nodeMap[$(this).attr('srcnodeid')])
            srcPin = thisPatch.nodeMap[$(this).attr('srcnodeid')].addOutputPin($(this).attr('srcpinname'), undefined);
          if (dstPin==undefined && thisPatch.nodeMap[$(this).attr('dstnodeid')])
            dstPin = thisPatch.nodeMap[$(this).attr('dstnodeid')].addInputPin($(this).attr('dstpinname'), undefined);
            
          if (srcPin && dstPin) {
            var link = false;
            for (var i=0; i<thisPatch.linkList.length; i++) {
              if (thisPatch.linkList[i].fromPin.node.id==srcPin.node.id &&
    					    thisPatch.linkList[i].fromPin.pinname==srcPin.pinname &&
    							thisPatch.linkList[i].toPin.node.id==dstPin.node.id &&
    							thisPatch.linkList[i].toPin.pinname==dstPin.pinname) {
                link = thisPatch.linkList[i];
    					}
            }
    
            if (!link) {
              link = new VVVV.Core.Link(srcPin, dstPin);
              srcPin.connectionChanged();
              dstPin.connectionChanged();
              thisPatch.linkList.push(link);
              for (var i=0; i<srcPin.values.length; i++) {
                dstPin.setValue(i, srcPin.getValue(i));
              }
              dstPin.setSliceCount(srcPin.getSliceCount());
            }
              
            if (syncmode=='complete')
              newLinks[srcPin.node.id+'_'+srcPin.pinname+'-'+dstPin.node.id+'_'+dstPin.pinname] = link;
          }
        });
        
        if (syncmode=='complete') {
          _(oldLinks).each(function(l, key) {
            if (newLinks[key]==undefined) {
              if (VVVV_ENV=='development') console.log('removing '+l.fromPin.pinname+' -> '+l.toPin.pinname);
              var fromPin = l.fromPin;
              var toPin = l.toPin;
              l.destroy();
              fromPin.connectionChanged();
              toPin.connectionChanged();
              toPin.markPinAsChanged();
              if (toPin.reset_on_disconnect)
                toPin.reset();
            }
          });
          oldLinks = {};
          _(newLinks).each(function(l, key) {
            oldLinks[key] = l;
          });
        }
      }
      
      if (this.resourcesPending<=0 && ready_callback) {
        ready_callback();
        ready_callback = undefined;
      }
      
    }
    
    /**
     * Recursively fetches and returns all subpatches inside a patch
     * @return {Array} an array of {@link VVVV.Core.Patch} objects
     */
    this.getSubPatches = function() {
      var ret = [];
      for (var i=0; i<this.nodeList.length; i++) {
        if (this.nodeList[i].isSubpatch) {
          ret.push(this.nodeList[i]);
          ret = ret.concat(this.nodeList[i].getSubPatches());
        }
      }
      return ret;
    }
    
    /**
     * Sets the {@link VVVV.Core.MainLoop} object of the patch and all containing subpatches
     * @param {VVVV.Core.MainLoop} ml
     */
    this.setMainloop = function(ml) {
      this.mainloop = ml;
      for (var i=0; i<this.nodeList.length; i++) {
        if (this.nodeList[i].isSubpatch) {
          this.nodeList[i].setMainloop(ml);
        }
      }
    }
    
    /**
     * Called always after the patch has been evaluated
     * @abstract
     */
    this.afterEvaluate = function() {
      
    }
    
    /**
     * Called always after the patch has been modified using {@link VVVV.Core.Patch.doLoad}
     * @abstract
     */
    this.afterUpdate = function() {
      
    }
    
    /**
     * Returns the VVVV XML string representing the patch, ready to be saved
     * @return {String}
     */
    this.toXML = function() {
      var $patch = $("<PATCH>");
      var $bounds = $("<BOUNDS>");
      $bounds.attr("type", "Window");
      $bounds.attr("width", parseInt(this.windowWidth * 15));
      $bounds.attr("height", parseInt(this.windowHeight * 15));
      $patch.append($bounds);
      
      var boundTypes = ["Node", "Box"];
      for (var i=0; i<this.nodeList.length; i++) {
        var n = this.nodeList[i];
        $patch.append(n.serialize());
      }
      for (var i=0; i<this.linkList.length; i++) {
        var l = this.linkList[i];
        $patch.append(l.serialize());
      }
      
      var xml = '<!DOCTYPE PATCH  SYSTEM "http://vvvv.org/versions/vvvv45beta28.1.dtd" >\r\n  '+$patch.wrapAll('<d></d>').parent().html();
      xml = xml.replace(/<patch/g, "<PATCH");
      xml = xml.replace(/<\/patch>/g, "\n  </PATCH>");
      xml = xml.replace(/<node/g, "\n  <NODE");
      xml = xml.replace(/<\/node>/g, "\n  </NODE>");
      xml = xml.replace(/<bounds/g, "\n  <BOUNDS");
      xml = xml.replace(/<\/bounds>/g, "\n  </BOUNDS>");
      xml = xml.replace(/<pin/g, "\n  <PIN");
      xml = xml.replace(/<\/pin>/g, "\n  </PIN>");
      xml = xml.replace(/<lonk/g, "\n  <LINK");
      xml = xml.replace(/<\/lonk>/g, "\n  </LINK>");
      return xml;
    }
    
    /**
     * Evaluates the patch once. Is called by the patch's {@link VVVV.Core.MainLoop} each frame, and should not be called directly
     */
    this.evaluate = function() {
      if (this.resourcesPending>0) // this.resourcesPending is >0 when thirdbarty libs or subpatches are loading at the moment
        return;
      if (print_timing) {
        var nodeProfiles = {};
        var start = new Date().getTime();
        var elapsed = 0;
      }
      var invalidNodes = {};
      var terminalNodes = {}
      for (var i=0; i<this.nodeList.length; i++) {
        if (this.nodeList[i].getDownstreamNodes().length==0 || this.nodeList[i].auto_evaluate || this.nodeList[i].delays_output) {
          terminalNodes[this.nodeList[i].id] = this.nodeList[i];
        }
        invalidNodes[this.nodeList[i].id] = this.nodeList[i];
      }
      if (print_timing)
        console.log('building node maps: '+(new Date().getTime() - start)+'ms')
      
      
      function evaluateSubGraph(node) {
        //console.log("starting with "+node.nodename+" ("+node.id+")");
        var upstreamNodes = node.getUpstreamNodes();
        _(upstreamNodes).each(function(upnode) {
          if (invalidNodes[upnode.id]!=undefined && !upnode.delays_output) {
            evaluateSubGraph(upnode);
          }
        });
        
        if (node.dirty || node.auto_evaluate || node.isSubpatch) {
          if (print_timing)
            var start = new Date().getTime();
          if (node.auto_nil && !node.isSubpatch && node.hasNilInputs()) {
            _(node.outputPins).each(function(outPin) {
              outPin.setSliceCount(0);
            });
          }
          else {
            try {
              node.evaluate();
            }
            catch (e) {
              console.log('VVVV.Js / Error evaluating '+node.nodename+': '+e.message);
            }
            node.dirty = false;
            
            _(node.inputPins).each(function(inPin) {
              inPin.changed = false;
            });
          }
          if (print_timing) {
            if (!nodeProfiles[node.nodename])
              nodeProfiles[node.nodename] = {count: 0, dt: 0};
            elapsed = new Date().getTime() - start;
            nodeProfiles[node.nodename].count++;
            nodeProfiles[node.nodename].dt += elapsed;
            console.log(node.nodename+' / '+node.id+': '+elapsed+'ms')
          }
        }
        delete invalidNodes[node.id];
        
        return true;
      }
      
      _(terminalNodes).each(function(n, id, index) {
        //console.log('starting anew '+n.nodename);
        if (invalidNodes[n.id]!=undefined)
          evaluateSubGraph(n);
      });
      
      if (print_timing) {
        _(nodeProfiles).each(function(p, nodename) {
          console.log(p.count+'x '+nodename+': '+p.dt+'ms');
        });
        var start = new Date().getTime();
      }
      this.afterEvaluate();
      if (print_timing)
        console.log('patch rendering: '+(new Date().getTime() - start)+'ms')
      
      print_timing = false;
    }
    
    $(window).keydown(function(e) {
  
      // ctrl + alt + T to print execution times
      if (e.which==84 && e.altKey && e.ctrlKey)
        print_timing = true;
    });
    
    // actually load the patch, depending on the type of resource
    
    if (/\.v4p[^<>\s]*$/.test(ressource)) {
      this.nodename = ressource;
      var that = this;
      var path = ressource;
      if (this.parentPatch)
        path = VVVV.Helpers.prepareFilePath(ressource, this.parentPatch)
      if (!VVVV.Patches[path]) {
        $.ajax({
          url: path,
          type: 'get',
          dataType: 'text',
          success: function(r) {
            that.doLoad(r, function() {
              VVVV.Patches[path] = VVVV.Patches[path] || [];
              VVVV.Patches[path].push(that);
              if (that.success)
                that.success();
              that.afterUpdate();
            });
          },
          error: function() {
            if (that.error)
              that.error();
          }
        });
      }
      else {
        that.doLoad(VVVV.Patches[path][0].toXML(), function() {
          VVVV.Patches[path].push(that);
          if (that.success)
            that.success();
          that.afterUpdate();
        });
      }
    }
    else {
      this.doLoad(ressource, function() {
        if (this.success) this.success();
      });
      
    }
    
    // bind the #-shortcuts
    function checkLocationHash() {
      var match = window.location.hash.match('#([^\/]+)\/'+thisPatch.ressource+'$');
      if (match) {
        console.log('launching editor ...');
        var ed = new VVVV.Editors[match[1]]();
        ed.enable(thisPatch);
      }
    }
    checkLocationHash();
    
    $(window).bind('hashchange', function() {
      checkLocationHash();
    });
    
    
  }
  
  
}
VVVV.Core.Patch.prototype = new VVVV.Core.Node();
VVVV.js Copyright © 2012-2013 The contributors to the VVVV.js project.
Documentation generated by JSDoc 3.3.0-alpha4 on Thu May 08 2014 13:12:34 GMT+0200 (Mitteleuropäische Sommerzeit) using the DocStrap template.