[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-multimedia
Subject:    Re: Easy MCOP! The Component extension proposal.
From:       Stefan Westerfeld <stefan () space ! twc ! de>
Date:       2000-03-06 1:35:27
[Download RAW message or body]

   Hi!

On Sun, Mar 05, 2000 at 11:09:09PM +0000, Nicolas Brodu wrote:
> Though very new to MCOP (about one week...), I've dug in the code and started
> implementing things of my own. You'll find below some thoughts and ideas,
> halfway done already, but I ask for some feedback/comments/help/flames before 
> continuing.
Great. I'm really happy to see some feedback ;)

> This mail is quite long, sort of compensate for the traffic on this list...
> 
> Here we go. First the motivation. If you look at the testflow examples, you'll
> notice that each module definition is about 15 lines, and contains 95% of
> administrative code (i.e. code just there to set up things).

That is right. However, consider this the API for the visual artsbuilder,
which you can use to build flow graphs "without" C++ API. The essential
idea is, that artsbuilder will never be able to directly write things like

  mixer->setInputLevel(1,0.14);

because it doesn't even know that there is a mixer. Rather, all objects
should have so much meta information that they describe themselves. Arts-
builder then can provide a property editor for objects that he doesn't
know. Similar to the way Delphi for instance provides the ability to put
together all objects, without knowing how they work or even knowing their
existence at the time Delphi was written.

Qt doesn't have this, but they are working hard on making this work soon,
as I can see from their new Qt-2.1 property inventions.

For MCOP these things will work in conjunction with the InterfaceRepository.
You can go do any object and ask: hey. What properties do you have. What
streams? What methods? What are the parameter names? How do the involved
structures look like? This is what artsbuilder will be able to use to
configure/connect/combine objects without knowing them.

First of all, here is the current testflowcxx.cc. I couldn't go to bed
without having a better example in the CVS ;).

//////////////////////////////////////////////////////////////////////////////

#include "artsflow.h"
#include "debug.h"
#include "flowsystem.h"
#include <stdio.h>
 
/*
 * After getting a comment back why that API looks so broken...: the API
 * used in testflow and testdynflow is not the API you'll use when programming
 * C++ with MCOP, the C++ API looks like that:
 */

/* possible enhancements after Nicolas Brodu's mail */
 
/* that code should go into MCOP or something similar */
 
#define creator(class)                                                  \
class * class ## _create(const string& subclass = __STRING(class))      \
{                                                                       \
    Object_skel *skel = ObjectManager::the()->create(subclass);         \
    assert(skel);                                                       \
    class *result = (class *)skel->_cast(__STRING(class));              \
    assert(result);                                                     \
    return result;                                                      \
}
 
creator(Synth_FREQUENCY);
creator(Synth_WAVE_SIN);
creator(Synth_PLAY);
creator(ExecutionManager);
 
void connect(Object *from, string fromPort, Object *to, string toPort)
{
    from->_node()->connect(fromPort, to->_node(), toPort);
}
 
void start(Object *tostart)
{
    tostart->_node()->start();
}

/* enhancements end */

int main()
{
    Dispatcher dispatcher;
 
    // object creation
    Synth_FREQUENCY_var freq = Synth_FREQUENCY_create();
    Synth_WAVE_SIN_var sin = Synth_WAVE_SIN_create();
    Synth_PLAY_var play = Synth_PLAY_create();
 
    // object initialization
    freq->_node()->setFloatValue("frequency",440.0);
 
    // object connection
    connect(freq,"pos",sin,"pos");
    connect(sin,"outvalue",play,"invalue_left");
    connect(sin,"outvalue",play,"invalue_right");
 
    // start all objects (maybe we should group objects like with QWidget
    // parents and such?)
    start(freq);
    start(sin);
    start(play);
 
    // go
    dispatcher.run();
}
//////////////////////////////////////////////////////////////////////////////

> My goal is to keep
> all the MCOP features, but remove all those administrative tasks and ultimately
> be able to write something like this (concepts from Qt and VTK):

I've commented the current API in between:

> 	MP3Reader mp3("mysong.mp3");          // object created directly
MP3Reader_var mp3 = new MP3Reader_impl(); // uglier with factories, see below
mp3->filename("mysong.mp3");              // in idl: attribute string filename;

> 	AudioProducer au(Reference(argv[1])); // use MCOP reference/string
AudioProducer_var au = AudioProducer::_fromString(argv[1]);

> 	Mixer mix(2);                         // variable number of ports
Synth_MULTI_ADD_var mix = new Synth_MULTI_ADD_impl();    // factory thing again
                                          // variable number of ports do resize
										  // themselves upon connect

> 	mix.inputLevel(1,.80);                // standard method call
> 	mix.inputLevel(2,.50);
Currently no possibility to do this.

> 	AudioPlayer *play = new AudioPlayer;  // dynamic allocation works also
AudioPlayer_var play = new AudioPlayer();

> 	connect(&mp3,&mix,"input1");          // connect the streams. No need 
mp3->_node()->connect("output",mix->_node(),"input");

> 	connect(&au,&mix,"input2");           // to tell the port name when a 
au->_node()->connect("output",mix->_node(),"input");

> 	connect(&mix,play);                   // default port is defined
mix->_node()->connect("output",play->_node(),"input");

> 	exec();                               // run this
mp3->_node()->start();
add->_node()->start();
au->_node()->start();
play->_node()->start();

> No more administrative code, and it'is now clear what it does (I don't know
> about you, but filling all those Port/PortDesc/ModuleDesc/StructureDesc with
> stacks of arguments everywhere is not that intuitive to me).

Ok, as you see, there is a better API (if you want to code on C++ level).
However it has clearly weaknesses. That other kind of API is meant for
"machines" only.

The long term goal for artsbuilder is that you can implement a new MCOP
object not only by coding (C++), but by clicking it together as signal
flow graph.

> To do this, I've defined a new keywork "component" for the idl. It's exactly the
> same as an interface for now, except that this allow the generation of the
> necessary code automatically (halfway implemented). In the future, some more
> keywords (like default) could be used (to specify a default port to connect to
> in a given direction in this example) with the components.

I haven't understand what code exactly you generate and how components
and interfaces differ, I'm sorry.

Default ports seem to be useful, as there is often only one input port and
one output port. The connect syntax is currently ugly as well. I see that.

The current connect however finds out automatically which ports is incoming
and outgoing. So you can write

mp3reader->_node()->connect("output",soundcard->_node(),"input");
soundcard->_node()->connect("input",mp3reader->_node(),"output");

if that is with defaultports and a new syntax, suppose you have a filter
defined like that:

interface Filter {
	in default audio port input;
	out default audio port output;
};

and you have two filters filter1 and filter2.

filter1->_node()->connect(filter2);

could now mean:
- connect filter1 input to filter2 output 
- connect filter1 output to filter2 input

Probably the best would be to have one global connect which is just

connect(Object *fromObject,[string fromPort],Object *toObject,[string toPort]);

while the fromPort and toPort may be omitted if there is a default port
defined.

> Line by line walk through (and state of art of what I implemented at present):
> 
> The first feature above, constructors (as in MP3Reader mp3("mysong.mp3")) could
> also use an idl keyword. Easy too implement though I code them directly for now.
> Creating from a reference: I coded it, don't know if it works.
> Variable number of ports: Should be feasible, but I didn't think about it more
> than that yet.
> Dynamic allocation: No problem, I solved the reference counting stuff in both
> automatic and dynamic allocation.
> Connecting: Should work, but could not test it. I also coded a setValue function
> for those input ports that don't connect but have a value instead.
> Executing: Coded, but doesn't work. Argl. I balked off there. Stefan, do we
> really need to derive from SynthModule to be able to use the port connecting
> system? I've spent hours looking in the code already and know better how it
> works now, but I've encountered this problem an hour ago and decided to talk
> about all this before going further on (It might save me some time).

SynthModule provides start() and stop() functionality, this is the idea
behind deriving.

> Technical question:
> This factory stuff, how is it supposed to work exactly? I mean, if you don't
> create one with the REGISTER_IMPLEMENTATION macro then you cannot use your
> implementation (warned in object.cc, IIRC), and if you cannot use the macro if
> you don't have a constructor without arguments. But then, why does the system
> want to create a new instance using the factory if you already have one. Is
> there a problem with the reference counting?

About factories:

The idea is not to use new. This allows you to create objects without even
knowing that an implementation exists. A nice example is the following
(taken from dispatcher.cc, latest snapshot).

    string globalCommName
            = MCOPUtils::readConfigEntry("GlobalComm","TmpGlobalComm");
 
    Object_skel *gcSkel = objectManager->create(globalCommName);
    assert(gcSkel);
    _globalComm = (GlobalComm *)gcSkel->_cast("GlobalComm");
    assert(_globalComm);                                                        

What this does is:

- globalCommName is read from a configuration file (global communication
  is required to find for instance the secret cookie using for authentication)

  the default is TmpGlobalComm, which will store that information in
  /tmp/mcop-<username>/...

  However, in arts/x11globalcomm, there is a class implemented called
  X11GlobalComm, which does the same via X11 server.

- the second line, the create statement creates now a GlobalComm object. The
  funny thing is: if you set GlobalComm=X11GlobalComm in .mcoprc, it will
  try to create an X11GlobalComm object, for which no implementation is
  available. Thus, ObjectManager will look in
  $KDEDIR/lib/X11GlobalComm.mcopclass, and will really find an entry there:

  Library=libx11globalcomm.la

  which will in turn cause him to load this file dynamically as extension.

- the third line is a cast - ObjectManager::create() returns an Object_skel*,
  which is a generic object pointer, and we'd like to have a GlobalComm
  object, so we need to cast.

But as these four lines are always reoccuring, I've been thinking what would
be the ideal solution. One possibility would be to generate an extra create
call per object which does the cast (maybe the assert, too)?

So the code could be merged to:

    string globalCommName
            = MCOPUtils::readConfigEntry("GlobalComm","TmpGlobalComm");
 
    _globalComm = GlobalComm::_create(globalCommName);

Another option would be a create macro or something like that, which could
look like

    _globalComm = MCOP_CREATE(GlobalComm,globalCommName);

Another elegant solution for _var objects seems to be

    Synth_PLAY_var synthPlay("Synth_PLAY");

About those _var and reference counting and stuff:

The problem is that there are base classes for everything, and then _skel
and _stub classes derived from that. This enables you to use each object
that you could use locally also remotely.

Suppose you have

interface Player {
	void play();
	void stop();
};

Then:

	{
		Player_var p = Player::_fromString(aReference);
		p->play();
		p->stop();
	}

could mean that there is a local implementation of a Player (perhaps a
Player_impl *), that is used directly (no bells and whistles). However,
it could also mean that there is a remove implementation of a Player in
another process, and that you are really talking to a Player_stub, which
when you call play() generates a message and sends it to the remote server
and there the message causes the Player to play something.

Thus:

	Player p("foo.wav");

won't work, because Player is just an abstract base class for both,
Player_stub and Player_skel, while Player_stub is autogenerated and
Player_skel is what you derive Player_impl from.

Suppose you write

foo::bar()
{
	Player_impl pi("foo.wav");
	remoteObject->listenTo(pi);
}

then the allocation on the stack might destroy all the trick. Here,
remoteObject may have created itself a copy of pi (using pi->_copy()),
and may still be "listening" to pi after the return of foo::bar(), and
by that way refer to a non-existing object as the thing from the stack
gets freed.

> Well, assuming this has no effect, I declared a constructor() and registered.
Maybe we could do pseudo constructors like

interface Player {
	attribute string filename;
	Player(filename);
};

which would mean: if Player::create("Player","foo.mp3") is called, then put
the foo.mp3 into the filename attribute? Hmm sounds a bit ugly, too.

> Problem, it now wants a SynthModule. Hence my question, do we need a SynthModule
> to use the port connection system?

Currently: yes. SynthModule implements initialize(), start() and such, which
are used to setup audio.

> Last thing, I noticed that the stream can only handle floats (audio_data) or
> strings (string_data). Is there a way to really have byte stream?

Well, what you saw was the artsbuilder api, which is not really complete.
What you should look at is what you can declare in interfaces.

You can declare

// just a normal mcop structure
struct VideoFrame {
	long width, height;
	sequence<long> contents;
};

interface Monster {
	// these are implemented
	in audio stream foo;
	in asynchronous byte stream somebytes;
	in multi audio stream xx;

	// these should be implemented (custom type, also usable for midi)
	in asynchronous VideoFrame stream videoIn;

	// maybe asynchronous audio makes also sense
	in asynchronous audio stream asyncAudio;

	// attributes - mostly complete
	attribute string filename;
	attribute VideoFrame aFrame;
	readonly attribute long aLong;
	[ and some others ]
}

And as you see: there are byte streams, but they are asynchronous. Read
the asynchronous streams docs in the CVS (or on http://www.arts-project.org,
there snapshots of the docs are linked).

> I know that there is already a lot to do on MCOP, and I'm ready to help there.
> But I also think that having an API as the one sketched above would be
> tremendously better than the current system (hence this proposition now, while
> MCOP can still be modified to some extent).
> 
> For information, I'm using the 20000229 snapshot and didn't spend time to merge
> in the network transparency. I'm using two components, FileReader and FileWriter
> that can produce and consume a byte stream as a basic example.
> 
> Anybody see a major flaw/impossibility in this?

Ok, my reply was even longer than your message ;-) - byte streams are
possible asynchronously, and artscat for instance uses them.

Hope to hear from you soon - as you see your mail did already improve things ;)

   Cu... Stefan
-- 
  -* Stefan Westerfeld, stefan@space.twc.de (PGP!), Hamburg/Germany
     KDE Developer, project infos at http://space.twc.de/~stefan/kde *-

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic