summaryrefslogtreecommitdiffstats
path: root/flow/virtualports.cc
diff options
context:
space:
mode:
Diffstat (limited to 'flow/virtualports.cc')
-rw-r--r--flow/virtualports.cc491
1 files changed, 491 insertions, 0 deletions
diff --git a/flow/virtualports.cc b/flow/virtualports.cc
new file mode 100644
index 0000000..76b5087
--- /dev/null
+++ b/flow/virtualports.cc
@@ -0,0 +1,491 @@
+ /*
+
+ Copyright (C) 2000 Stefan Westerfeld
+ stefan@space.twc.de
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ */
+
+#include "virtualports.h"
+#include <algorithm>
+#include <stdio.h>
+
+using namespace Arts;
+using namespace std;
+
+#undef VPORT_DEBUG
+
+/* virtual port connections */
+
+/*
+
+Port virtualization is used in the following case: suppose you have a module
+M which has an input port and an output port like that:
+
+input
+ V
+-----
+ M
+-----
+ V
+output
+
+But suppose the module M is implement as combination of other modules, for
+instance the effect of M is achieved by passing the signal first through an
+A and then through an B module. Clients connecting M should "really" connect
+A,B. For this virtualization is used.
+
+There are two kinds:
+
+- masquerading: which means (like in our case), the input of a module is really
+ implemented with the input of another module. So M's input could be really
+ implemented by A's input
+
+ there is also output masquerading, which would be for instance that M's
+ output is really implemented by B's output
+
+- forwarding: if M in our example would choose to do nothing at all, it could
+ simply forward its input to its output
+
+
+The interface for the user:
+
+MCOP will show the virtualize function to the user, which the user can use
+to delegate the services of a port to another module onto another port.
+
+- masquerading: in our case, for instance the user could call
+
+ m._node()->virtualize("inputport",a._node(),"inputport");
+
+ which would forward all input m gets on "inputport" to a's "inputport"
+
+- forwarding: in the same way, the user could call
+
+ m._node()->virtualize("inputport",m._node(),"outputport");
+
+ which would make m forward its input directly to its output
+
+The implementation:
+
+Virtualization is implemented here, inside the flow system, using a fairly
+complex forwarding strategy, which will have a graph which contains
+
+- "user-made" connections (made with connect())
+- "virtualize-made" connections, which can be either forwarding
+ (input to output port) or masquerading (input to input or output to output)
+
+Out of all these, the algorithm builds "real" connections, which are
+then really performed inside the flow system. If you change the "user-made"
+or "virtualize-made" connections, the "real" connections are recalculated.
+
+The "real" connections are created by the expandHelper function. They are
+called vcTransport here.
+
+The strategy expandHelper uses is to go back to a port which is only output
+port (non forwarded, non masqueraded), and then follow the graph recursively
+over vcMasquerade and vcForward edges until it reaches a port which is only
+input. Then it creates a real connection.
+
+Some tweaks are there which allow that not on any change at the graph, all
+real connections will be removed, but only these that could possibly be
+affected by this change, and then not all real connections are created new,
+but only those that could possibly be created by this virtual connection.
+
+Every VPort contains a pointer to the "real" port, to let the flow system
+know where the "real" connections where real data will flow must be made.
+
+*/
+
+VPortConnection::VPortConnection(VPort *source, VPort *dest, Style style)
+ :source(source),dest(dest),style(style)
+{
+ if(style != vcTransport)
+ {
+ list<VPortConnection *>::iterator i;
+
+ // remove transport connections ending at "source" (they will
+ // probably be forwarded/masqueraded elsewhere by this VPortConnection)
+ i = source->incoming.begin();
+ while(i != source->incoming.end())
+ {
+ if((*i)->style == vcTransport)
+ {
+ delete *i;
+ i = source->incoming.begin();
+ }
+ else i++;
+ }
+
+ // remove transport connections starting at "dest" (they will
+ // probably be forwarded/masqueraded elsewhere by this VPortConnection)
+ i = dest->outgoing.begin();
+ while(i != dest->outgoing.end())
+ {
+ if((*i)->style == vcTransport)
+ {
+ delete *i;
+ i = dest->outgoing.begin();
+ }
+ else i++;
+ }
+ }
+
+ // add to the connection lists
+ source->outgoing.push_back(this);
+ dest->incoming.push_back(this);
+
+ if(style == vcTransport)
+ {
+#ifdef VPORT_DEBUG
+ arts_debug("emit a connection consumer = %s, producer = %s",
+ dest->name(), source->name());
+#endif
+ dest->port->connect(source->port);
+ }
+ else
+ {
+ source->makeTransport(this);
+ }
+}
+
+VPortConnection::~VPortConnection()
+{
+#ifdef VPORT_DEBUG
+ cout << "~VPortConnection" << endl;
+#endif
+
+ if(style != vcTransport)
+ {
+ // remove transport connection which go through this connection
+ source->removeTransport(this);
+ }
+
+ // remove this connection from the lists
+ list<VPortConnection *>::iterator ci;
+
+ ci = find(source->outgoing.begin(),source->outgoing.end(),this);
+ assert(ci != source->outgoing.end());
+ source->outgoing.erase(ci);
+
+ ci = find(dest->incoming.begin(),dest->incoming.end(),this);
+ assert(ci != dest->incoming.end());
+ dest->incoming.erase(ci);
+
+ if(style == vcTransport)
+ {
+#ifdef VPORT_DEBUG
+ arts_debug("delete connection %s -> %s",dest->name(), source->name());
+#endif
+ dest->port->disconnect(source->port);
+ }
+
+ // reestablish all connections which started/ended here before
+ if(style != vcTransport)
+ {
+ list<VPortConnection *>::iterator i;
+ stack<VPortConnection *> todo;
+
+ // reestablish transport connections which were ending at source...
+ for(i = source->incoming.begin(); i != source->incoming.end(); i++)
+ if((*i)->style != vcTransport) todo.push(*i);
+
+ // ... and starting at dest
+ for(i = dest->outgoing.begin(); i != dest->outgoing.end(); i++)
+ if((*i)->style != vcTransport) todo.push(*i);
+
+ // we need to do this with the stack as makeTransport can affect the
+ // incoming/outgoing lists by adding new vcTransport connections
+ while(!todo.empty())
+ {
+ todo.top()->source->makeTransport(todo.top());
+ todo.pop();
+ }
+ }
+
+#ifdef VPORT_DEBUG
+ cout << "~VPortConnection done" << endl;
+#endif
+}
+
+/*---------------------- virtual port implementation ----------------------*/
+
+VPort::VPort(Port *port) :port(port)
+{
+#ifdef VPORT_DEBUG
+ cout << "VPort: " << name() << endl;
+#endif
+}
+
+VPort::~VPort()
+{
+#ifdef VPORT_DEBUG
+ cout << "~VPort: " << name() << endl;
+#endif
+ while(!incoming.empty()) delete *incoming.begin();
+ while(!outgoing.empty()) delete *outgoing.begin();
+#ifdef VPORT_DEBUG
+ cout << "~VPort done" << endl;
+#endif
+}
+
+bool VPort::makeVirtualizeParams(VPort *forward, VPort*& source, VPort*& dest,
+ VPortConnection::Style& style)
+{
+ source = dest = 0;
+ // masquerading
+ if((port->flags() & streamIn) && (forward->port->flags() & streamIn))
+ {
+ // input: data flow direction is from us to the "forward" port
+ // XXX?
+ source = this;
+ dest = forward;
+ style = VPortConnection::vcMasquerade;
+ }
+ else if((port->flags() & streamOut) && (forward->port->flags() & streamOut))
+ {
+ // output: data flow direction is from the "forward" port to us
+ // XXX?
+ source = forward;
+ dest = this;
+ style = VPortConnection::vcMasquerade;
+ }
+ // forwarding
+ else if((port->flags() & streamIn) && (forward->port->flags() & streamOut))
+ {
+ source = this;
+ dest = forward;
+ style = VPortConnection::vcForward;
+ }
+ else if((port->flags() & streamOut) && (forward->port->flags() & streamIn))
+ {
+ source = forward;
+ dest = this;
+ style = VPortConnection::vcForward;
+ }
+ return source != 0;
+}
+
+/**
+ * a->virtualize(b) means, that the functionality that port a should provide
+ * (e.g. produce or consume data) is really provided by port b
+ */
+void VPort::virtualize(VPort *forward)
+{
+ VPort *source, *dest;
+ VPortConnection::Style style;
+
+ if(makeVirtualizeParams(forward,source,dest,style))
+ {
+#ifdef VPORT_DEBUG
+ cout << "virtualize ... source (producer) is " << source->name() <<
+ " dest (consumer) is " << dest->name() << endl;
+#endif
+ new VPortConnection(source,dest,style);
+ }
+}
+
+void VPort::devirtualize(VPort *forward)
+{
+ VPort *source, *dest;
+ VPortConnection::Style style;
+
+ // XXX?
+ if(makeVirtualizeParams(forward,source,dest,style))
+ {
+ list<VPortConnection *>::iterator i;
+ for(i = source->outgoing.begin(); i != source->outgoing.end(); i++)
+ {
+ if((*i)->source == source && (*i)->dest == dest
+ && (*i)->style == style)
+ {
+ delete (*i);
+ return;
+ }
+ }
+ }
+}
+
+void VPort::setFloatValue(float value)
+{
+ if(outgoing.empty())
+ {
+ AudioPort *aport = port->audioPort();
+ assert(aport);
+ aport->setFloatValue(value);
+ }
+ else
+ {
+ list<VPortConnection *>::iterator i;
+ for(i=outgoing.begin();i != outgoing.end(); i++)
+ {
+ VPortConnection *conn = *i;
+ assert(conn->style == VPortConnection::vcMasquerade);
+
+ conn->dest->setFloatValue(value);
+ }
+ }
+}
+
+void VPort::connect(VPort *dest)
+{
+ VPortConnection *conn;
+ if(port->flags() & streamOut)
+ {
+ conn = new VPortConnection(this,dest,VPortConnection::vcConnect);
+ }
+ else
+ {
+ conn = new VPortConnection(dest,this,VPortConnection::vcConnect);
+ }
+}
+
+void VPort::disconnect(VPort *dest)
+{
+ if(port->flags() & streamOut)
+ {
+ list<VPortConnection *>::iterator ci = outgoing.begin();
+ while(ci != outgoing.end())
+ {
+ assert((*ci)->source == this);
+ if((*ci)->dest == dest && (*ci)->style==VPortConnection::vcConnect)
+ {
+ delete (*ci); // will remove itself from the outgoing list
+ return;
+ }
+ ci++;
+ }
+ }
+ else
+ {
+ if(dest->port->flags() & streamOut)
+ {
+ dest->disconnect(this);
+ return;
+ }
+ }
+}
+
+void VPort::expandHelper(VPortConnection *conn, int state, VPort *current,
+ VPort *source, VPort *dest, bool remove)
+{
+ list<VPortConnection *>::iterator ci;
+
+#ifdef VPORT_DEBUG
+ cout << "expandhelper state " << state << " name " << current->name() << endl;
+#endif
+
+ if(state == 1) /* state 1: scan backward for output ports */
+ {
+ if(current->incoming.empty())
+ {
+ if(current->port->flags() & streamOut)
+ expandHelper(conn,2,current,current,dest,remove);
+ }
+ for(ci = current->incoming.begin(); ci != current->incoming.end();ci++)
+ {
+ assert((*ci)->style != VPortConnection::vcTransport);
+ expandHelper(conn,1,(*ci)->source,source,dest,remove);
+ }
+ }
+ else if(state == 2) /* state 2: output port expansion */
+ {
+ assert(current->port->flags() & streamOut);
+
+ for(ci = current->outgoing.begin(); ci != current->outgoing.end();ci++)
+ {
+ /* xconn=0 ensures that only paths are counted which contain conn */
+ VPortConnection *xconn = conn;
+ if(*ci == conn) xconn = 0;
+
+ if((*ci)->style == VPortConnection::vcMasquerade)
+ {
+ expandHelper(xconn,2,(*ci)->dest,source,dest,remove);
+ }
+ else if((*ci)->style == VPortConnection::vcConnect)
+ {
+ expandHelper(xconn,3,(*ci)->dest,source,(*ci)->dest,remove);
+ }
+ }
+ }
+ else if(state == 3) /* state 3: input port expansion */
+ {
+ assert(current->port->flags() & streamIn);
+
+ for(ci = current->outgoing.begin(); ci != current->outgoing.end();ci++)
+ {
+ /* xconn=0 ensures that only paths are counted which contain conn */
+ VPortConnection *xconn = conn;
+ if(*ci == conn) xconn = 0;
+
+ if((*ci)->style == VPortConnection::vcMasquerade)
+ {
+ // XXX ?
+ expandHelper(xconn,3,(*ci)->dest,source,(*ci)->dest,remove);
+ }
+ else if((*ci)->style == VPortConnection::vcForward)
+ {
+ expandHelper(xconn,2,(*ci)->dest,source,dest,remove);
+ }
+ }
+
+ if(current->outgoing.empty() && conn == 0)
+ {
+ if(remove)
+ {
+ // delete exactly one transport connection
+
+ bool removeOk = false;
+ ci = current->incoming.begin();
+ while(ci != current->incoming.end() && !removeOk)
+ {
+ if((*ci)->source == source && (*ci)->dest == dest
+ && (*ci)->style == VPortConnection::vcTransport)
+ {
+ delete (*ci);
+ removeOk = true;
+ }
+ else ci++;
+ }
+ assert(removeOk);
+ }
+ else
+ {
+ new VPortConnection(source,dest,VPortConnection::vcTransport);
+ }
+ }
+ }
+}
+
+void VPort::makeTransport(VPortConnection *conn)
+{
+ expandHelper(conn,1,this,0,0,false);
+}
+
+void VPort::removeTransport(VPortConnection *conn)
+{
+ expandHelper(conn,1,this,0,0,true);
+}
+
+const char *VPort::name()
+{
+ if(_name.empty())
+ {
+ _name = port->parent->object()->_interfaceName() + "." +
+ port->name();
+ }
+ return _name.c_str();
+}