Open mvz opened 9 years ago
I'm struggling with this one. Is there a solution on the horizon?
I'm afraid I have nothing planned at the moment.
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.
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.
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.
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
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?
Your patch looks reasonable, by the way.
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
@kugel- thanks, I'll take a look.
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.
I'd like to come back to this. What do you think is missing from the patch I posted above?
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.