2

New properties for GObject

 1 year ago
source link: https://gitlab.gnome.org/GNOME/glib/-/issues/1437
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

New properties for GObject

The existing property machinery for GObject is inefficient in various ways:

  • GValue boxing and unboxing all over the place
    • data is copied and freed
    • locks are held
    • even va_list paths end up into GValue, though we already know the final type
  • entry points duplication
    • massive switch inside GObject implementations for set and get
    • the set_property() and get_property() code ends up calling into public API
    • double the validation, double the fun
    • g_object_set() ends up notifying even without state change, unless the class explicitly opts out
  • GParamSpec
    • weird API full of special cases
    • does value validation separately from the GObject instance that installed the property
    • value validation is separate from C types, and always explicit instead of being tied to the type itself (e.g. uint8 needs a uint + [0, 255] GParamSpec)
  • GParamSpecPool
    • singleton instance shared by all classes
    • lock ALL the things
    • run time introspection is expensive
    • decoupled from GObject
  • properties installed at class initialization requires creating classes
    • bad for GObject introspection
    • bad for documentation
    • real library code needs to run, instead of simply using the type system
    • side effects (open display server connections; network; type instantiation; locks)
  • properties on interfaces require playing games with overrides, instead of tying a property to a set/get pair of virtual functions on the interface itself, to be overridden by implementations

Issue #408 (closed) outlined a new API for declaring GObject properties that dealt with validation, value boxing and unboxing, and added automatic generation of accessors to reduce duplication.

Unfortunately it's still missing a solution to avoid a costly property metaclass look up, and it got stuck there. Over the years, we identified a way forward to eliminate the global GParamSpec pool and replace it with per-class GProperty pointers, stored in the class private data structure using a constant offset. This would turn property look up into a cheaper operation than the current implementation.

Blockers

The missing pieces to land this work are:

  • ensure that private data for a GTypeClass is allocated in the same way as the private data for a GTypeInstance, and can be retrieved via pointer arithmetic
  • every time a new property is installed on a class, it gets installed into the private data for the class
  • property accessors generator macros can use the property id and the class private offset to access the GProperty instance from an instance pointer

Issues

  • ideally, we'd really like to have all the property metadata known by the type we register a GType; this would allow us to store the property metadata at a known offset from the start of the class pointer; in practice, this may not really possible because of mixed old-style/new-style inheritance hierarchies. We will need to create a per-class storage pointer to the actual property metaclass storage. New style classes could make it easier by declaring the number and type of properties to be created at type registration time, while old-style classes without properties would pay at most a sizeof(void*) cost in storage.
0 of 3 checklist items completed · Edited 2 years ago by Emmanuele Bassi

Tasks

0

No tasks are currently assigned. Use tasks to break down this issue into smaller parts.

Linked items

Link issues together to show that they're related. Learn more.

Activity

  • So, let's rough up an API design.

    WARNING: Incoming wall of text.

    The main problem we have is that shoving a bunch of property declarations inside G_DEFINE_TYPE_WITH_CODE is a bit messy. Additionally, that would make mixing classes with new style properties and classes with old style properties basically impossible. The last time I had to migrate GTK and GIO to the new instance private data it took a while, and it was a boring patch to review — and people still haven't finished porting their code now, 5 years and change later, judging from the amount of deprecation warnings coming from g_type_class_add_private() when building GNOME libraries and applications.

    Ideally, 99% of the API should be hidden behind convenience macros, because boilerplate is boring.

    Headers

    Easy stuff:

    The header stuff is pretty trivial, and are just declarations; it allows us to have a stable definition and naming scheme for setters and getters, and we could even end up parsing them from gobject-introspection.

    Source

    Buckle up, kids, because this is where it gets complicated.

    First, we move the declaration of properties outside of class_init, in order to have a global variable with the property identifier:

    This would expand to a C99 declaration:

    Then, inside class_init:

    Finally, accessors would be generated using the property offsets:

    Side effects:

    • this wouldn't work in non-C99 toolchains, and it wouldn't work in C++; we can ignore non-C99 toolchain, as we've bumped our compiler requirements, and C++ can use the underlying, non-macro API
    • additional API that needs to live beside the old one for the next 10 years
    • no need to change the allocation layout; properties are global to the type, and thus can be accessed directly
    • the existing API would work, as we'd keep installing properties in the GParamSpecPool for backward compatibility, but we wouldn't access the properties through that mechanism any more, outside of g_object_set|get_property() and serialisation formats like GtkBuilder; per-property access would still be faster in those cases
    • global declarations can only be constants, so this breaks type restriction on properties, like enums, flags, object, and boxed types; we could add the restriction later on, but it's kind of inconvenient
    • the amount of global variables makes it even more unlikely we'll be able to support unloading types in the future, which means GTypeModule won't really be able to use this API

    The last one is the problematic bit we were trying to avoid by storing the GProperty inside the class private data. I'm not entirely sure we still want to care, considering that you can't write plugins for anything that implements a GTK widget, as types are static anyway; but it's a thing, so we may want to still support it. The problem is that we cannot store all the properties inside the GObjectClass because each class may have its own overrides, and we cannot create a private class data structure implicitly because some types already have a private class data structure.

    We could create the per-class storage when allocating the class data if we knew the number of properties when we register the type:

    Of course, this prevents language bindings from using this API, because they do like doing things like adding properties post-class instantiation. It also makes the declaration a bit messy:

    The property registration becomes:

    This requires changing the layout of the memory allocation for GTypeClass, and adding a new field inside the node type to store the reserved space for properties, so we can use it when allocating the class.

    Alternatively, if we wanted to have this inside the type definition, we could use:

    Adding properties and generating accessors would follow the same mechanism as above.

    The main side effect to this scheme is that we're now up to four different places where we declare or define properties. It would be much nicer if we could just memcpy() the property declarations inside the type node, and when instantiating a class create the GProperty instances ourselves; this would remove the need for adding properties inside the class_init altogether in the common case.

    The major side effect is the re-implementation of finding and listing properties; we would have to write a new implementation that would:

    • find_property:

      • check if the type class has properties added at type registration time
        • if it does, walk through the listed properties for the given name
          • if we find the given name, we return the GParamSpec we found
        • if it doesn't, we walk to the parent class, and repeat the check
      • if we don't find anything, we go through the GParamSpecPool for an old style property
    • list_properties:

      • as above, but collect all properties instead of comparing and returning the first match

    Implementing properties for interfaces is another messy thing; ideally, we want properties on interfaces to map to virtual functions on the interfaces themselves, but we cannot store the GProperty on the interface structure, unless we follow the same over-allocation scheme for GTypeInterface; we'd also need a bunch of different macros for generating the appropriate accessors and boilerplate.

    Edited 4 years ago by Emmanuele Bassi
  • Looking at interfaces, I think we don't really have to do anything for their properties.

    g_object_interface_install_property add the pspec to the pool, and we call object_interface_check_properties in class_init, but thats about it. At runtime, the object property is used, which can be declared either the old-school way, or the new way.

  • Whats a little fuzzy in your api outline is GProperty vs GPropertyDecl - what gives ? Are they the same thing now?

  • The main problem with the initial GProperty implementation was that we had to access the GProperty out of the global pool, which slowed down the default C code path.

    Whats a little fuzzy in your api outline is GProperty vs GPropertyDecl - what gives ? Are they the same thing now?

    That's one of the options for the API: putting the property metadata in a structure that is statically allocated inside the source, so we can access it without having to access the GObject instance—which is, typically, the thing that requires locking.

    The other option is to move the property metadata into the class private data, so we can access it as an offset, e.g.

  • Another option is to pull what the Rust bindings for GObject have done, have have a vfunc that returns the array of properties:

    which would be equivalent to:

Please register or sign in to reply

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK