.. _operators-bindings:

Operators and Bindings
===========================

The KL code that runs within the dependency graph and event graph is contained in objects called Operators.  The same operator can be bound to multiple Nodes and EventHandlers by using different Bindings (see below) that share the same Operator.

Operator Creation
---------------------

An Operator is created by calling :samp:`fabricClient.RT.createOperator`, passing the name of the Operator as the first argument.  Operator names must be unique and not shared with any Nodes, Events or EventHandlers.

.. code-block:: none
  
  >>> op = fabricClient.DG.createOperator("doSomething")
  >>> 

Setting Operator Source Code
-------------------------------

The source code contained in the Operator is set using the Operator's :samp:`setSourceCode` method.  It takes a string containing the KL source code.

.. note:: Source code is first usually loaded from an external resource, using eg. the :samp:`fabricClient.loadResourceURL` function, rather than being included as an inline string as is done in these examples.

After setting the source code, you can check if any warnings or errors were generated by the KL compiler by calling the :samp:`getDiagnostics` method, which returns an array of objects describing the warnings/errors, including the level (warning/error), line number, column number and message.  You can later retrieve the source code by calling the :samp:`getSourceCode` method.

.. code-block:: none
  
  >>> op.setSourceCode("operator entry( io Scalar result, in Size index, in Container self ) { result = 3.14 }")
  >>> op.getDiagnostics()
  [{u'column': 82, u'line': 1, u'level': u'error', u'desc': u'syntax error, unexpected }, expecting ;', u'filename': u'(unknown)'}]
  >>> op.setSourceCode("operator entry( io Scalar result, in Size index, in Container self ) { result = 3.14; }")
  >>> op.getDiagnostics()
  []
  >>> 

Setting the Operator Entry Point
-----------------------------------

In addition to source code, an operator needs an entry point, which is the name of the KL operator (see the :ref:`KLPG`) in the source code that should be called when the operator is invoked.  Note that this *must* be an KL operator and not a KL function.  The entry point is specified by calling the :samp:`setEntryPoint` method.  By specifying the source code and entry point separately, it is possible to have multiple possible entry points into the same source code.

.. code-block:: none
  
  >>> op.setEntryPoint('entry')
  >>> op.getEntryPoint()
  'entry'
  >>> 

Bindings
---------------------

To make an operator run on a Node or EventHandler, you must create a Binding object which describes what data the KL operator arguments are bound to when the operator is run.

.. note:: It is possible to have multiple bindings that all share a single operator.

A binding object is created by calling :samp:`fabricClient.DG.createBinding`, and you set the Operator called by the Binding by calling the Binding's :samp:`setOperator` method.  This operator can later be retrieved by calling the Binding's :samp:`getOperator` method.

.. code-block:: none
  
  >>> binding = fabricClient.DG.createBinding()
  >>> binding.setOperator(op)
  >>> binding.getOperator()
  <fabric._OPERATOR object at 0x1958e50>
  >>> 

Binding Parameter Layouts
-------------------------------

The way in which the KL operator arguments are bound is specified by calling the Binding's :samp:`setParameterLayout` method.  :samp:`setParameterLayout` takes a single parameter that is an array of strings.  The length of the array must be equal to the number of parameters taken by the KL operator in the Operator's source code, and each string describes what data that parameter should bind to.

Such string is of the format :samp:`{object}.{member}`, or :samp:`{object}` for a special usage which we will detail later.  The :samp:`{object}` part refers to what Node, EventHandler or Event object contains the data to be bound, as follows:

- If :samp:`{object}` is :samp:`self`, the data is contained on the object where the binding is attached

- For Bindings that live on Nodes, :samp:`{object}` is the name of the direct dependency Node that contains the data

- For Bindings that live on EventHandlers, :samp:`{object}` is the name of an ancestor EventHandler in the call chain as specified by a call to its :samp:`setScopeName` method, or a Node that is connected to the EventHandler through a call to the EventHandlers's :samp:`setScope` method.

The :samp:`{member}` part refers to the data member on the object specified by :samp:`{object}`, with support for the following additional syntaxes:

- If :samp:`{member}` is simply the name of a member (eg. "position"), the parameter will be bound to that member once for each slice.  The operator will be invoked once for each slice of the Node, potentially in parallel.  The KL parameter in the operator must be an :samp:`io` parameter whose type is the type of the member.

- If :samp:`{member}` is the name of a member followed by :samp:`[]` (eg. :samp:`position[]`), the parameter will be bound to a variable-length array that contains the data for *all* the slices for that member.  The length of the array will be equal to the slice count of the Node.  The KL parameter in the operator must be an :samp:`io` parameter whose type is a variable-length array of the type of the member.

- If :samp:`{member}` is :samp:`index`, the parameter will be index of the current slice for which the operator is being executed.  The parameter must be an :samp:`in` parameter of type :samp:`Index` (or, equivalently, :samp:`Size`)

- If :samp:`{object}` is specified (instead of :samp:`{object}.{member}`), then the parameter must be of type :samp:`Container`, which allows you to get or set the Node slice count in KL. Calling :samp:`Container`'s :samp:`resize(Size)` method will immediately change the slice count of the Node, and the :samp:`size()` method will return its current slice count. The :samp:`resize(Size)` method requires that the parameter is specified as :samp:`io`.

.. code-block:: none
  
  >>> binding.setParameterLayout( ["self.result","self.index","self"] )
  >>> node = fabricClient.DG.createNode("foo")
  >>> node.bindings.append(binding)
  >>> node.getErrors()
  [u"binding 0: operator 'doSomething': node 'self': parameter 1: member 'result': 'result': no such member"]
  >>> node.addMember("result","Scalar")
  >>> node.getErrors()
  []
  >>> 

.. note:: Even when a Binding binds a parameter to a member of a dependency of a Node, rather than a member of the Node itself, the parameter must still be declared as an :samp:`io` parameter in KL.  This is a limitation of the system which will be removed in the future; in fact, it will become required that members of non-:samp:`self` objects be bound to :samp:`in` parameters.