mvz / gir_ffi

Auto-generate bindings for GObject based libraries at run time using FFI
https://github.com/mvz/gir_ffi/wiki
GNU Lesser General Public License v2.1
145 stars 14 forks source link

Remember existing Ruby wrappers for GObjects #63

Open mvz opened 9 years ago

mvz commented 9 years ago

If a Ruby wrapper already exists for a given GObject, GirFFI should not create a new wrapper but instead return the existing one. Ruby-GNOME2 and PyGobject store the wrapper in the GObject's qdata.

Keeping this wrapper around allows storing the properties for a derived type as Ruby instance variables instead of extending the C struct of the parent type.

kugel- commented 7 years ago

I'm struggling with this one. Is there a solution on the horizon?

mvz commented 7 years ago

I'm afraid I have nothing planned at the moment.

kugel- commented 7 years ago

I'm playing with ClassBase::direct_wrap to maintain a lookup table of previous object allocations (indexed by ptr.address). I can't seem to make it work though.

mvz commented 7 years ago

Once this is working I can stop storing user-defined properties in C struct fields, which should reduce the some of the ref_count increases that happen in #82.

kugel- commented 7 years ago

I'd be willing to work on this but I don't know where to start. ClassBase::direct_wrap doesn't seem to be the right place. When I store the allocated object in a hash (and do the same in ClassBase#assign_pointer) and return that on subsequent allocations I get an error when calling the interface: (/home/kugel/dev/libpeas.git/tests/libpeas/.libs/lt-extension-rbffi:2318): GLib-GObject-CRITICAL **: Object class ExtensionRbFFI doesn't implement property 'object' from interface 'PeasActivatable'. Somehow the class information gets lost.

kugel- commented 7 years ago

The below, basic patch seems to help. I can observe that my instance is re-used and its instance variable survive (the native code creates the object and then immediately calls a interface vfunc). I'm sure it's not complete yet. I guess at least removing from @object_cache must be implemented to avoid reference cycles.

What do you think of it?

diff --git a/lib/gir_ffi/class_base.rb b/lib/gir_ffi/class_base.rb
index 4b401bc..0e71939 100644
--- a/lib/gir_ffi/class_base.rb
+++ b/lib/gir_ffi/class_base.rb
@@ -11,6 +11,13 @@ module GirFFI
     extend Forwardable

     GIR_FFI_BUILDER = Builders::NullClassBuilder.new
+    @object_cache = {}
+
+    def self.inherited(klass)
+      klass.class_eval do
+        @object_cache = {}
+      end
+    end

     attr_reader :struct
     def_delegators :@struct, :to_ptr
@@ -73,7 +80,11 @@ module GirFFI
     # do any casting to subtypes or additional processing.
     def self.direct_wrap(ptr)
       return nil if !ptr || ptr.null?
-      obj = allocate
+      # try to use existing object from cache
+      unless obj = @object_cache[ptr.address]
+        obj = allocate
+        @object_cache[ptr.address] = obj
+      end
       obj.__send__ :assign_pointer, ptr
       obj
     end
@@ -100,7 +111,12 @@ module GirFFI
     end

     def assign_pointer(ptr)
+      _self = self
       @struct = self.class::Struct.new(ptr)
+      self.class.class_eval do
+        @object_cache[ptr.address] = _self
+      end
+      @struct
     end
   end
 end
mvz commented 7 years ago

Let's back up a little bit. Can you show me what you need this feature for? My original intention was to use it to be able to store properties as Ruby instance variables, but with the recent enhancements of the user-defined properties that seems less urgent. So, can you show me a bit more of what you're trying to achieve?

mvz commented 7 years ago

Your patch looks reasonable, by the way.

kugel- commented 7 years ago

Can you show me what you need this feature for?

I'll describe my project, hopefully that explain this and other requirements.

If you don't know libpeas: It's a library originated in the GNOME world that implements a plugin interface and loaders for non-C languages. C-based programs can use this, other ones too since libpeas also provides introspection data for itself. Programs using this library can easily support written in other languages, most notably python and lua at the moment. I'm working on adding Ruby to the list. Some prominent users of libpeas are gedit, eog, gitg and Geany (through a plugin that I developed).

So, for a libpeas loader there are some requirements: 1) Usable objects can be created from native code; the native code works with the GObject (or derived) pointer. 2) The objects implement GObject interfaces, 3) the loaders have some bindings to the GTK stack, preferably GI-based as libpeas does not provide such by itself. The bindings can be gir_ffi or ruby-gnome2 but I'm focussing on gir_ffi.

For 1, there is a specialized function that the programs call (peas_engine_create_extension()), but once created this way the plugin code is only executed through interface methods, bypassing libpeas, where the programs call it directly (e.g. peas_activatable_activate() which is a method of the PeasActivatable interface).

For 2, Ruby context is only entered through the interface methods (setup by gir_ffi). Between those invocations the proxy object (Ruby object here) must maintain state.

My current state is that I call into Ruby when an extension is created. The backing loader implementation of peas_engine_create_extension() calls create_extension() in a Ruby helper script, which in turn does the usual allocate and send :initialze pattern. The loader then extracts the GObject * through #to_ptr.address

I've pushed my current state onto github, see https://github.com/kugel-/libpeas/tree/ruby. The tests give some idea how the library is used, for example here: https://github.com/kugel-/libpeas/blob/ruby/tests/libpeas/extension-rbffi.c#L52

mvz commented 7 years ago

@kugel- thanks, I'll take a look.

mvz commented 7 years ago

Between those invocations the proxy object (Ruby object here) must maintain state.

Just a note: My intent was always that subclasses defined in Ruby should still store all their state in properties, which are stored in C data. This is a little more complicated than using instance variables, but it should work. Ruby-GNOME uses the other approach and stores even properties as ivars.

kugel- commented 7 years ago

I'd like to come back to this. What do you think is missing from the patch I posted above?