Re: Exporting the Gtk+ object system to interpreters



Tim Janik <timj@gtk.org> writes:

> GtkTypeQuery does not expose internal private data of the type system,
> and in the future it can be extended as desired, e.g.

Yes.  I still have reservations, but I can live with it no problem.
The reservations being: My gtk_type_get_info didn't expose internal
info either, and I don't see the usefulness of the extensability.  We
have to make certain guarantees about the performance characteristics
of these functions, and I don't want to use a function that can
suddenly turn from a simple means to copy information out of a hidden
place into one that goes over the whole type system and does some
serious statistics business.  When this need arises (and can not be
serviced from outside the Gtk+ code), we can add another, more
expensive function, where everybody understands that it is more
expensive.  KISS.

> what do you mean by gtk_type_query "did *not*" work?

Last time I looked, it had this implementation:

    GtkTypeQuery*
    gtk_type_query (GtkType type)
    {
      GtkTypeNode *node;

      LOOKUP_TYPE_NODE (node, type);
      if (node)
	{
	  GtkTypeQuery *query;

	  query = g_new0 (GtkTypeQuery, 1);
	  query->type = type;
	  query->type_name = node->type_info.type_name;
	  query->object_size = node->type_info.object_size;
	  query->class_size = node->type_info.class_size;
	}

      return NULL;
    }

QUERY is never returned, you always get NULL.

> please read the C comments below more carefully. default handlers
> need to be setup in class_init() functions and from then on are
> considered static data.

Why should they be static?  Granted, most of them will be static, but
it would sure be handy to change a default handler at any time while
developing a program in a more dynamic language.

> we shouldn't break that convention, not even for interpreter
> bindings, i couldn't even come up with a reason why you'd want to
> subsequently change default handlers.

But that's only you.  You don't hold the complete wisdom of the world
in your little hands, Tim.  Neither do I, of yourse, but I have a
different part of that wisdom.  We should try to implement the union
of both of our parts, not only the intersection.

> thus, the data argument does not need a destroy notifier.

Yes, it might not need one, but it is part of the "full" callback
specification.  We can ignore it if the data is never actually
destroyed, but it should be included in the interface for setting a
default handler.

In my current implementation of gtk_signal_set_class_function_full I
can't call the destroy notifier either, because I can't track the
number of copies I have made of a full default handler.  I don't
consider this a big problem for the time being, but it's a bug,
nevertheless.

> also, default handlers for existing C classes cannot (and actually
> *must not*) be changed. thus, you may only use
> gtk_object_class_set_unmarshalled_handler() for newly derived
> classes, from within their class initializer.

Yes, I want to enforce part of this rule in my Scheme bindings.  It
will only let you set the default handler for classes defined in
Scheme.  I don't think we need to restrict the setting of default
handlers to the class initializer, tho.

> > >   GTK_RUN_NO_HOOKS      = 1 << 4,
> > > + GTK_RUN_UNMARSHALLED  = 1 << 5
> > 
> > I'm not sure if we need this one.  Just setting marshaller=NULL should
> > be indicative enough.
> 
> for one, i want to keep marshaller=NULL as a special hint for future
> extensions where we implement automated marshaller lookups upon
> signal creation. for another, generic code like interface builders
> need to be able to figure whether a specific signal does not provide
> marshalling abilities at all. this can then be queried through
> GtkSignalQuery.signal_flags&GTK_RUN_UNMARSHALLED.

I think we should design our changes to the signal system in such a
way that it never hurts to add a default marshaller to a signal that
wasn't created with one.  That is, when someone (the Gtk+ code itself
or some external code) adds a default marshaller to a signal that
didn't previously had one, that should not alter the behaviour of the
signal in any way, except that it now can do things that it needs a
default marshaller for, namely allowing connections from handlers that
don't have their own marshaller.

Phew, let me try to say that again in smaller bites.  A signal without
a default marshaller is just like any other signal, except that it
cannot handle connections from handlers (including default handlers)
that need a default marshaller.  The absence of a default marshaller
does not imply any intentions to change the semantics of a signal, it
just implies that the creator of the signal couldn't provide a default
marshaller.  When someone else can fill that void, that is a good
thing and it only extends the things one can do with that signal, it
does not in any way change the behaviour of the things it could do
before.

So, using NULL to indicate "dear Gtk+ type code, give me one of your
handy default marshallers, if you please.  Thank you." is perfectly
fine for the reason I want to use NULL for the default marshaller.  It
is compatible (even identical) with your interpretation, I think.

One thing needs to be considered, tho: what happens when the Gtk+ type
code *can't* provide a default marshaller?  I would prefer to just
carry on, while you would probably say to abort the program with an
error message.  Upon the utterance of which I would reply thusly:
Defer the error message to the act of connecting a handler that needs
the missing default marshaller.

> besides that, the flag is required by the signal system itself to
> invoke such signals with the GtkUnmarshalledFunc prototype,

Yes, but that's an implementation detail, not a new type of signal.

> i'll explain later why that prototype is required.

Is not! ;-)

> as much as i'd like to keep the above [GtkCallbackMarshall]
> prototype as an interface, it's not possible to get that going with
> default handler chaining. you're right in that at least guint
> signal_id can be eliminated, i meant that as a convenience for you
> actually.

The instance_type can be omitted just the same.  The first indication
why this is the case is that the existing default handlers don't need
it either.

Look at this code from gtkwindow.c:

    static void
    gtk_window_destroy (GtkObject *object)
    {
      [...]
      GTK_OBJECT_CLASS (parent_class)->destroy (object);
    }

This is the default handler of the GtkWindow widget for the "destroy"
signal.  It `knows' this.  It knows the type of the object it is
associated with, and it knows the appropriate parent_class.  It knows
that it is a "destroy" default handler.  This knowledge does not have
to be passed to it from the Gtk+ signal or type system.  It has been
`built in' by the writer of the code.  All this can (and has to) be
built into a Scheme or Perl default handler, too, by the writer of
that handler, not by the general glue code.

More abstractly, using artificial invoke_default_handler and
set_default_handler functions and signals that have no signal-specific
parameters:

Suppose you have these two functions:

  void
  set_default_handler (GtkType   klass,
                       gchar    *signal_name,
                       void    (*handler) (GtkType instance_type,
                                           void *data),
                       void     *data);

    Set the default handler of KLASS for the signal denoted by
    SIGNAL_NAME to be HANDLER, and associate it with DATA.

  void
  invoke_default_handler (GtkType  klass,
                          gchar   *signal_name);

    Invoke the default handler of KLASS for the signal denoted by
    SIGNAL_NAME.  This invokes the function last set via

      set_default_handler (klass, signal_name, data, handler);

    as

      handler (klass, data);

    or, when set_default_handler has never been called for the
    combination of KLASS and SIGNAL_NAME, it is equivalent to

      invoke_default_handler (type_parent (klass), signal_name);

    or, when KLASS has no parent type, it does nothing at all.

Do we agree that this is the semantic of default handlers?  You can
coerce the current practice into this `formalism' by replacing things
like

    klass->signal_name = handler

with

    set_default_handler (klass, "signal_name", handler, NULL);

and likewise

    klass->signal_name ();

with

    invoke_default_handler (klass, signal_name);

Now, you can see from this definitions that a HANDLER is *always*
invoked with an INSTANCE_TYPE parameter set to the KLASS that was used
when setting the HANDLER itself (unless the same handler has been used
for more than one invokation of set_default_handler).  That is, after

    set_default_handler (klass, signal_name, handler, data);

HANDLER will always be invoked as

    handler (klass, data);

when used to act as the default handler for KLASS and SIGNAL_NAME.
That is, the association between a handler and a klass are made at
set_default_handler time, not at invoke_default_handler time.  Thus,
you can use a handler that knows the klass it will be invoked with,
and you do not need to pass the klass in.

The only situation when a handler can be invoked with varying
instance_type parameters is when it has been specified in more than
one call to set_default_handler for different klasses/signal_names.
Suppose we have

    set_default_handler (klass1, signal_name1, handler, data1);
    set_default_handler (klass2, signal_name2, handler, data2);

Now, HANDLER can be invoked as either

    handler (klass1, data1);

or

    handler (klass2, data2);

As you can see, the instance type is no longer associated with the
precise handler.  But, it is still associated with the call to
set_default_handler, and thus, DATA1 and KLASS1, as well as DATA2 and
KLASS2 will always be associated.  We again can avoid passing the
instance_type parameter because we can put it into the things hanging
off of the data cookie.

For my Scheme bindings, I wouldn't even need to do this, because the
Scheme procedures can do it for themselves if they want to.

[ The exact same argumentation can be used to eliminate the signal_id
  parameter, btw, and I'm sure you used it already for discovering
  this fact.
]

Below, you mention "(chain-parent)", which is supposed to
automatically invoke the default handler for the `current' signal of
the parent of the `current' type.  I don't want to have such a thing
at this stage, and in any case, implementing it is outside the scope
of the Gtk+ type system (or even my Scheme bindings for that matter).

True, CLOS like object systems have something like call-next-method,
which invokes the next method in the list of effective methods of a
specific activiation of a generic function.  This is a useful feature
and eventually we will have such a thing in Guile (there's already a
STKlos port to Guile, but I haven't really tried it out), and
eventually I hope we will have a Guile GUI toolkit that uses the Guile
object systems (I think its API will look more like CLIM but it will
use Gtk+ underneath).  But that is then, and now is now.

In any case, there will be no problem whatsoever to channel the needed
information into a default handler.  Suppose we can set default
handlers, but they do not receive the instance_type or signal_id
parameters.  Then we want to add your chain-parent functionality.

It goes like this:

    (define (set-handler-with-chaining class signal handler)
      (define (chain-parent)
        (invoke-handler (type-parent class) signal))
      (set-handler class signal (lambda () (handler chain-parent))))

I have passed the chain-handler function as a parameter to the handler
to ensure reentrancy, but we could just as well use a `fluid' to get
rid of that.

> since GTK_RUN_UNMARSHALLED signals are a very specialized type of signals,

They shouldn't be.

> and since i want to preserve the existing interface for existing and
> upcoming code, the creation of GTK_RUN_UNMARSHALLED signals needs to
> be routed through an extra interface.

I don't think this is necessary or helpful, but I accept your authority.

> i originally intended to [...]

Ok, that is all fine with me.
 
> besides that, i also don't understand what GtkSignalFunc is meant to
> be used for.

It is also part of the "full" callback interface.  With this "full"
interface, you can either connect handlers that use the default
marshaller (by setting FUNC!=NULL and MARSHAL==NULL), or you can
connect handlers with their own marshaller (MARSHAL!=NULL, FUNC is
irrelevant).

I originally intended FUNC to be passed to MARSHAL as well, so that
MARSHAL could then call it if it wanted to (analogous to
GtkSignalMarshaller in Gtk+ (not GtkSignalMarshal)), but Owen(?)
decided against it.

> > > C functions that want to connect to unmarshalled signals (though
> > > that behaviour is questionable, it should be taken care of) will be
> > > invoked with the same signature as the default handler, i.e. that of
> > > GtkUnmarshalledFunc.
> > 
> > Again, I would strongly prefer to keep the old mechanism for this.  C
> > functions can already be connected to signals without a default
> > marshaller via gtk_signal_connect_full.  They would then be invoked as
> > a GtkCallbackMarshal.
> 
> i'm not talking about marshaller-connections here like you can specify
> with gtk_signal_connect_full() when passing func=NULL, i'm talking
> about normal gtk_signal_connect() invokations here.

Ahh, I think I understand now where our interpretions differ.  The
status quo is:

    Non-default handlers can be connected as plain C functions that
    make use of the default marshaller provided by the signal.
    Non-default handlers can also be connected as custom-marshalled
    functions that bring their own marshaller.

    Default handlers can be set as plain C functions that make
    use of the default marshaller provided by the signal.

    To connect a non-default handler, you use gtk_signal_connect_full
    which allows for both plain C functions as well as
    custom-marshalled ones.  You can also use gtk_signal_connect as a
    convenience function for plain C function.
    To set a default handler, you just put a pointer to the function
    in the appropriate class field.

(I gloss over how to invoke handlers here.)

I want to even out this unsymmetric situation so that non-default
handlers and default handlers are more `equal'.  This would lead to

    Non-default handlers can be connected as plain C functions that
    make use of the default marshaller provided by the signal.
    Non-default handlers can also be connected as custom-marshalled
    functions that bring their own marshaller.

    Default handlers can be set as plain C functions that make use of
    the default marshaller provided by the signal.
    Default handlers can also be set as custom-marshalled functions
    that bring their own marshaller.

    To connect a non-default handler, you use gtk_signal_connect_full
    which allows for both plain C functions as well as
    custom-marshalled ones.  You can also use gtk_signal_connect as a
    convenience function for plain C function.

    To set a default handler, you use gtk_signal_set_default_handler
    which allows for both plain C functions as well as custom
    marshalled ones.  For convenience, you can also put a pointer to
    the function in the appropriate class field for plain C default
    handlers.

with the consequence that it is possible to have a useful setup that
doesn't use default marshallers at all.  This is fortunate, because
interpreter bindings likely can't provide default marshallers.

You, on the other hand, want to add a new type of signal that
expressively has no default marshaller and has few things in common
(API-wise) with the existing normal signals:

    Non-default handlers can be connected to normal-signals as plain C
    functions that make use of the default marshaller provided by the
    normal-signal.
    Non-default handlers can also be connected as custom-marshalled
    functions that bring their own marshaller.

    Default handlers can be set to normal-signals as plain C
    functions that make use of the default marshaller provided by the
    normal-signal.

    To connect a non-default handler to a normal-signal, you use
    gtk_signal_connect_full which allows for both plain C functions as
    well as custom-marshalled ones.  You can also use
    gtk_signal_connect as a convenience function for plain C function.

    To set a default handler for a normal-signal, you just put a
    pointer to the function in the appropriate class field.

  In additon, there are unmarshalled-signals, which use a different
  calling convention.  While the plain C functions of handlers for
  normal-signals are invoked with their parameters `on the stack' like
  any other C function, the handler functions of unmarshalled signals
  are called with their arguments in a GtkArg array.  This gives all
  handler functions of all unmarshalled signals the same signature and
  thus there is no concept of a marshaller for unmarshalled-signals.

    Non-default handlers can be connected to unmarshalled-signals, but
    the connector has to be aware that this is a unmarshalled-signal.

    Default handlers can be set for unmarshalled-signals, but the
    setter has to be aware that this is a unmarshalled-signal.

    To connect a non-default handler to a unmarshalled-signal, you use
    gtk_signal_connect_full, but with a different calling convention
    as for normal-signals (namely, FUNC is now the function that is
    invoked with the unmarshalled arguments, not MARSHAL (which is
    meaningless here)).  You can also use gtk_signal_connect as a
    convenience function because of the different calling convention.

    To set a default handler for a unmarshalled-signal, you use
    gtk_object_class_set_default_handler.  Because of the different
    calling convention, you can also just out a pointer to the
    function in the appropriate class field.

    To write a generic function that connects a custom-marshalled
    handler to an arbitrary signal (either normal or unmarshalled),
    one has to figure out whether the signal is normal or unmarshalled
    and then choose one of the two different conventions for using
    gtk_signal_connect_full.

Does that express your view?

I'm not going to say that your view of the things is invalid, but I
still like my version better because it is conceptually simpler.

> function_offset=0 has already a meaning, it is an indicator for user signals
> that don't provide default handlers at all.

If a user signal is a signal that does not provide a default handler
than a user signal can be recognized by the fact that it does not
provide a default handler.  When signals can have a default handler
even when they have FUNCTION_OFFSET==0, then user signals can still
refrain from having a default handler simply be not setting one.  As
always, I don't see the problem.

Further, I'm quite sure that user signals only have no default handler
because they *can't* have a FUNCTION_OFFSET!=0, for purely technical
reasons, not because they don't want to.  When there is a way to have
default handlers even with FUNCTION_OFFSET==0, then user signals would
certainly be happy to have one.  There wouldn't even need to be the
concept of user signals, because the regular signals are general
enough.

I think the rest of your objections stem all from our different
interpretations outlined above.

> > Anyone who has managed to read upto here is invited to Free and Open
> > beer, should we ever meet.
> 
> be carefull, i'm living in germany and could actually come around
> some day ;)

Yeah, and actually I do hope that that will happen.  You have earned
yourself quite a lot free beer for your work on Gtk.  I'd be very
happy to meet you in person so that we can fight out our differences
face to face. ;-) So, where do you live?  I'm living in Dortmund, with
occasional visits to Berlin and Munich.

- Marius

-- 
GNOME: Penguin in bondage.



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]