Re: Structs in gobject-introspection + Javascript



On Sat, 2008-11-08 at 18:14 -0500, Havoc Pennington wrote:
> Hi,
> 
> On Sat, Nov 8, 2008 at 5:07 PM, Owen Taylor <otaylor redhat com> wrote:
> > [ Sorry for the long cc: list. Do we need gnome-js-list gnome org? ]
> 
> It would be pretty low-traffic, but maybe so. I guess it could include
> both seed and gjs. As long as nobody minds, maybe it's simpler to just
> use language-bindings, since language-bindings is so low-traffic
> anyway. Anyway, no strong views.

Well, it probably depends on how much Javascript takes off within GNOME...
One message I didn't know where to send isn't reason to create a list
but we'll see.

I think it's important to have seed <=> gjs communication so they stay
compatible in how GObject is being mapped into Javascript.

> > 1. Structs are supported if they are:
> >
> > - Have no pointer fields. (They can have nested structures{1} and union
> >  fields). Call these simple structures.
> >
> > Or:
> >
> > - Are registered as boxed types with GObject
> >
> > Note that some structures, such as GdkRectangle or GdkColor are both
> > boxed and simple structures. {2}
> 
> Is it desirable to support not-registered-as-boxed structs, or could
> we just register them all as boxed?
> 
> Not sure it is materially harder to support non-boxed types, so maybe
> it doesn't matter.

In the "metacity => gnome-shell" world of taking some random C API and
exposing it to Javascript, it's nice to be able to not have to register
stuff as boxed that you don't need as boxed. Of course, as soon as you
want something as a signal parameter or property, then you need the
boxed registration anyways.

> One consideration is that some boxed, maybe GdkCursor (don't remember
> the examples) ref rather than copy in their copy function. I don't
> know if it ever makes a difference. I believe the rule is that if it's
> refcounted and mutable, it should be a GObject to be nicely bindable.
> Fixing GObject to avoid its current overhead (as discussed on
> whichever bug it is) would help here.

If it makes a difference, I think the API is buggy. But nothing I've
scoped out here should behave too badly with a refcounted mutable
object; it just means that clone() or a copy constructor won't work as
expected.

> > 4. In any place a a simple structure is expected, any other object that
> > has properties for all the objects fields can be used. So, you can do:
> >
> >  window.invalidate_rect({ x: 5, y: 5, width: 100, height: 100 }, true);
> 
> An interesting question this raises is whether you would ever bother
> (for simple structs) with the current boxed.c type of implementation
> where the JS object contains a C struct. I would say no; when you
> copy, just create a plain JS dict with the appropriate properties. So
> there would be no JSClass corresponding to simple structs.

Simple structures can have methods (clutter_color_from_pixel() say, though
there's a definitely an argument that that one should be a static method
instead...) So I think you want to keep them as Object subclasses. It's an 
implementation detail whether you keep a pointer to C structure internally, 
though it seems easier to me that way.

> For boxed structs, this does create a strange multiple-codepaths
> situation, if the boxed struct is simple also (is not opaque). For
> opaque boxed structs, the JSClass wrapping a C pointer is clearly
> necessary.
> 
> Perhaps implementation-wise, the issue is whether the struct is opaque
> or not... whether having the values of all struct fields is equivalent
> to having the struct. Not whether the struct is boxed. If the struct
> is not opaque, then there's never a need for a special JSClass, just
> always map as a plain JS object. if the struct is opaque, then we need
> a JSClass wrapping a boxed type.
> 
> Is it possible to have a boxed type with some non-opaque / visible
> fields, that still must be treated as boxed and not just copied by
> value? GdkEvent I guess is an example since copy by value breaks? But
> for GdkEvent, do we want to allow a plain object with the appropriate
> fields to substitute?

GdkEvent is about as bad as a special case as you can find - it's 
a discriminated union, it has pointer fields with special memory management,
etc. There are hidden fields that are only available if you use 
gdk_event_new() [see gdk_event_set/get_screen()]. My instinct would be
to avoid thinking about it as an example.

> I'm wondering if we can avoid multiple codepaths, where to marshal the
> args to something that takes struct Foo, we have to handle both a
> wrapper-object-for-C-struct and a random JS object. (Though, just
> doing one codepath and always using JS_GetProperty would work, it
> would be a lot less efficient than getting the wrapped C struct and
> then memcpy'ing it in the case where the wrapped struct exists)

I think you just need a "fill C structure from random JS object"
function. Then it works out pretty simply.

> Just thinking out loud, don't know
> 
> >  copy() - returns a new object that is a copy of this object [2]
> 
> clone() would be the usual name for this in JavaScript I think,
> various xpcom objects use clone() and prototype.js adds Object.clone
> 
> I don't think SpiderMonkey's Object class has clone() already but maybe it does.

Not in the docs if it does. I was looking around for examples to find
out what the standard name is.

> >  set() - assigns a value of another object of the same type to the
> >         object [3]
> 
> Does imports.lang.copyProperties() always do the same thing?
> 
> If someone passed in a dict as a struct, it would not have these
> methods. That could be a little surprising; perhaps it is cleaner if
> non-opaque structs are always just plain objects, and have no special
> methods at all. 

See my mention of simple structure methods above. I don't think you can
avoid a bit of a disjunction here. There are clearly traps when you
start having one bit of Javascript code calling another bit of
Javascript code with a "structure".

> Are copy() and set() really more useful on a rectangle
> than they would be on any other JS dictionary?

Well, the argument for set() pretty much involves my suggested mapping
for in-out functions. If you go with that mapping, then you need set()
to assign a new value for something like GtkTextIter.

> Under what circumstances would some JS code know it had a special
> GdkRectangle proxy with copy() and set(), vs. just a dictionary with
> x/y/width/height fields?

To avoid that trap, I think you would have to get rid of the "can use 
any object with the right fields" and require Gdk.Rectangle({...})
always. Maybe a cure worse than the disease?

> > So, equivalent to the size-request example above you can do:
> >
> > widget.connect('size-request',
> >                function(widget, request) {
> >                    request.set({ width: 100, height: 50 });
> >                });
> 
> In the size-request case, what is going on with the copying? Do we
> copy the Requisition, pass it to the handler, then set the original
> requisition back to the copy's new values?

Yes, that was my suggestion.

> > [1] Or is it better to do out structure parameters like you would do out
> > integer parameters - as a return value or an array of return values?
> > I generally like the suggested method better because it corresponds to C
> > code and to some of the existing GObject language bindings.
> 
> It seems like it's necessary to map out how some contrived method
> signatures would work, like:

Hmm, the consideration of consistency between method calls from
Javascript and callbacks to Javascript is an important one. I was mostly
thinking of the second case, but it's pretty clear:

 let rect = new Gdk.Rectangle();
 some_object.get_bounds(rect);

Is wrong. 

> struct Foo;
> 
> Foo* foobar(out Foo, out int, out Foo)
> 
> Are we saying that some out params (structs) are done in JS by passing
> in dictionaries, and some are return values, so:
> 
> let foo1 = {};
> let foo2 = {};
> [rval, intOut] = foobar(foo1, foo2);
> 
> And fields in foo1, foo2 would get set?
> 
> It seems a bit simpler to me if we just map all "out" params as return
> values (which is how it works now, iirc):
> 
> [rval, foo1, intOut, foo2] = foobar();

I'm pretty convinced by that consideration. I had one particular example
in mind - GtkTextBuffer ::insert-text/::delete-range where you have to
revalidate the iters. Hmm:
 
 do_insert_text : function(pos, text) {
    return GtkTextBuffer.prototype.do_insert_text.call(this, 
                                                       pos,
                                                       text.toLowerCase());
 }

That's actually not that bad. (Perhaps more annoying is the method call
side with:

 pos = buffer.insert(pos, text1);
 pos = buffer.insert(pos, text2);

But even that's only really bad if you think too hard about how much garbage is
being created :-)

Thanks for the comments.

- Owen




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