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

List:       kde-multimedia
Subject:    Re: Easy MCOP! The Component extension proposal.
From:       Nicolas Brodu <nicolas.brodu () free ! fr>
Date:       2000-03-06 16:28:02
[Download RAW message or body]

OK, this morning I skipped all the technical stuff because I didn't have time. 
Here is my answer.

Stefan Westerfeld wrote:
> 
> 
> void connect(Object *from, string fromPort, Object *to, string toPort)

That's what I did too, if you look the example in my previous mail.
You can check those files. They're completely unusable without the changes in
the rest of MCOP and will have to be rewritten to some extent after your
explanations, but it might give a better idea of what I proposed. Here they are:
http://nicolas.brodu.free.fr/component.cc
http://nicolas.brodu.free.fr/component.h

// Connect function overloaded for components with default port
void connect(Component* src, const string& output, Component* dest, const
string& input);
void connect(Component* src, const string& output, Component* dest);
void connect(Component* src, Component* dest, const string& input);
void connect(Component* src, Component* dest);
// setValue function overloaded for components with default port
void setValue(Component* c, const string& port, const float fvalue);
void setValue(Component* c, const string& port, const string& svalue);
void setValue(Component* c, const float fvalue);
void setValue(Component* c, const string& svalue);

But I guess for setvalue, a void* will be needed to allow custom types.

>     // 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();

In VTK they do something like:
	vtkSphereMapper *map = vtkSphereMapper::New();

And then when the object isn't needed anymore
	map->Delete();

This is clearer, and I'd prefer it that way if direct allocation is not possible
(cannot do 'vtkSphereMapper map;' in VTK, but I have a few ideas to get round
this). Looking at the code, they use a kind of factory also. I guess it's just
sensible.
 
>     // object initialization
>     freq->_node()->setFloatValue("frequency",440.0);

I aim at a direct freq.setFloatValue("frequency",440.0);
but I see the point in doing this that way.
 
>     // object connection
>     connect(freq,"pos",sin,"pos");
>     connect(sin,"outvalue",play,"invalue_left");
>     connect(sin,"outvalue",play,"invalue_right");

Same here.
 
>     // start all objects (maybe we should group objects like with QWidget
>     // parents and such?)
>     start(freq);
>     start(sin);
>     start(play);

Now, that's interesting. I didn't see you could start things. The SynthModule
interface?
 
> 
> > 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;

You still have those _var and _impl around. I did CORBA before, so I know about
them, but others might not. If nothing better, the vtkSphereMapper syntax above
is IMHO better.

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

Now that I think about it, AudioProducer au = Reference(argv[1])); might be
better yet, or even AudioProducer *au = Reference(argv[1])); which would solve
all problems by an operator definition.
And it's even compatible with the ::New() syntax. Hmmm... something to be done
there...

> 
> >       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

Wasn't even aware of this...

> 
> >       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();

But you then have to cast you object to an implementation class if you want to
use it.
> 
> >       connect(&mp3,&mix,"input1");          // connect the streams. No need
> mp3->_node()->connect("output",mix->_node(),"input");

We both agree here that this syntax isn't clear, especially for which is an
input and an output port.


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

I'll put something like this in exec() then...
 
> 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 point exactly. I'm no machine!

> 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.

This would be really great. But this doesn't prevent us from creating a good
compatible API that programmers could use directly (the 'xmms' example in my
other mail).

> 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.

It's just a matter of adding a keywork in the parser and generating a flag. No
worry there.

> Probably the best would be to have one global connect which is just
> 
> connect(Object *fromObject,[string fromPort],Object *toObject,[string toPort]);

See above, we both agree. But there is also a need for a setValue(...) function
for those input ports that don't connect.
 
> SynthModule provides start() and stop() functionality, this is the idea
> behind deriving.

I understand this now. Just that I was confused by the SynthModule name, since I
use a FileReader/Writer as examples, I didn't think looking at a synth module...
 
> About factories:

Thanks for the detail. It's a great idea indeed.

> 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);

I don't have the necessary knowledge about factories yet to comment on this.
But a macro saves coding time, and would be more understandable in this case.
(At least, you can see it creates something, even if you don't know about
factories).

> 
> Another elegant solution for _var objects seems to be
> 
>     Synth_PLAY_var synthPlay("Synth_PLAY");

Synth_PLAY::New() again?

> 
> 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.

I know this from CORBA.

> 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.

That's why I renamed the base class to _base and the implementation to the
interface name, because when you write Player p("foo.wav"); you implicitely want
an implementation.

> 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.

Yep, but if you create a local Player pi instance, then it's sensible the
remoteObject can only access it for the pi life time. Otherwise put it global,
dynamic, or something else (ex, a class private variable).
The solution there is to remove all the connections to this object in the pi
destructor.

> > 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.

Sounds to mee to. Since for me Player is actually the implementation (not the
_base class) I don't have this problem. The constructor would just be generated
as any other method. (Of course, the _base and _skel do not have it)

> 
> > 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.

OK. I'll look at the code.

> > 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 {

OK, that's fine for custom types. About video, though, I think it'll have to be
handled natively, after all MCOP stands for Multimedia... 
Don't look at me this way Stefan! I know it's time-consuming and all, and I
didn't mean for _you_ to do it explicitely. I'll help there, but will
concentrate on a good programming interface before (will save time, at the end).

> 
> 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).

Well, the 20000229 snapshot didn't have it, and I cannot update very often.

That's all for now...

Cheers,
Nicolas

-- 
A shortcut is the longest distance between two points. (unknown author)

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

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