In my new library I have I like to present stimuli to a user for psychological experiments. Such as presenting circles and other types of visual stimuli or sounds. I like to base my C library on glib and gobject, as that makes it relatively easy to have language bindings, and glib provides many handy algorithms/data structures for my library, so I don't have to write those myself. Many of my intended clients (researchers in the field of psychology/linguistics) might not be proficient in C, but might be relatively handy with a language such as python. So I'm using C to be able to meet the requirements of a psychological experimentation toolkit. I would like to know with millisecond precision when a stimulus is presented to the user. I think for this C is handy, but a friendly API for psychologist and for this Python/javascript API is handy. GObject aims to create bindings to these GObject based C libraries via GObject introspection and generally I'm really happy how this process works out.
I'm having some problems when I try to set a GObject property in python when python does not have a reference to a variable, or perhaps only one in the return tuple. If i'm using setter methods, the code runs just fine. So in the python fragment below, I'm experiencing problem when I do an assignment like this:
instance.prop.property_name = Psy.SomeNewObject()
Setting the property like this:
instance.set_property_name(Psy.SomeNewObject())
or
my_property_value = Psy.SomeNewObject()
instance.prop.property_name = my_property_value
Works like intended and I don't get any errors/warnings. So a small script below is something that I have in my test directory to see whether the bindings work nicely in python. In the example below I'm creating a Canvas (could be a window, or an offscreen egl managed canvas). I'm creating a Circle object which is drawn on the canvas. Circle is derived from an Abstract class PsyVisualStimulus. Each instance of PsyVisualStimulus has a color property, the color in which we would like to draw the VisualStimulus. The Color, Canvas and VisualStimulus all derive from GObject.
#!/usr/bin/env python
import gi
import tempfile
# All object in Psy.* derive from GObject and not GObject.InitiallyUnowned.
# Hence, they cannot have a floating reference. All objects/functions (should) use
# transfer annotations, to determine object lifetime, as is currently advised.
gi.require_version("Psy", "0.1")
from gi.repository import Psy
bg_color = Psy.Color(r=0.5, g=0.5, b=0.5)
fg_color = Psy.Color(r=1.0, g=0.0, b=0.0)
x, y = 0.0, 0.0
radius = 150.0
num_vertices = 100
canvas = Psy.GlCanvas.new(640, 480)
circle = Psy.Circle.new_full(canvas, x, y, num_vertices, radius)
canvas.set_background_color(bg_color) # this seems fine
circle.props.color = fg_color # this is fine as we have an ref in python
# The next line prints a warning in the shell, and I would like to fix this!!
circle.props.color = Psy.Color(r=0.5, g=0.5, b=0.5)
circle.props.color = fg_color
circle.play_for(
canvas.get_time().add(Psy.Duration.new_ms(16)), Psy.Duration.new_ms(50)
) # Draw circle for 50 ms.
canvas.iterate()
image = canvas.get_image()
image.save_path(tempfile.gettempdir() + "/red-circle.png", "png")
the line circle.props.color = Psy.Color(r=0.5, g=0.5, b=0.5) emits a runtime warning printed to the shell:
sys:1: RuntimeWarning: Expecting to marshal a borrowed reference for <Psy.Color object at 0x7f9e0ee26a80 (PsyColor at 0x5616651dba00)>, but nothing in Python is holding a reference to this object. See: https://bugzilla.gnome.org/show_bug.cgi?id=687522
The line below works precisely as intended and doesn't produce any warnings. So I must be doing something incorrectly in my C library and I would like to have it fixed. Following the link in the warning does not bring me to something I understand and it seems quite old and perhaps outdated. I'll demonstrate fragments of my C code that that the python code above runs through in order to set the color property of a PsyVisualStimulus The Circle in the fragment above derives from PsyVisualStimulus, as PsyVisualStimulus is abstract I cannot instantiate those directly.
// header
#define PSY_TYPE_VISUAL_STIMULUS psy_visual_stimulus_get_type()
G_DECLARE_DERIVABLE_TYPE(
PsyVisualStimulus, psy_visual_stimulus, PSY, VISUAL_STIMULUS, PsyStimulus)
G_MODULE_EXPORT PsyColor *
psy_visual_stimulus_get_color(PsyVisualStimulus *self);
G_MODULE_EXPORT void
psy_visual_stimulus_set_color(PsyVisualStimulus *self, PsyColor *color);
// relevant parts of the implementation.
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(PsyVisualStimulus,
psy_visual_stimulus,
PSY_TYPE_STIMULUS)
static void
psy_visual_stimulus_set_property(GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PsyVisualStimulus *self = PSY_VISUAL_STIMULUS(object);
switch ((VisualStimulusProperty) property_id) {
// many other properties are omitted from this fragment
case PROP_COLOR:
psy_visual_stimulus_set_color(self, g_value_get_object(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
}
}
static void
psy_visual_stimulus_class_init(PsyVisualStimulusClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->get_property = psy_visual_stimulus_get_property;
object_class->set_property = psy_visual_stimulus_set_property;
object_class->dispose = psy_visual_stimulus_dispose;
PsyStimulusClass *stimulus_class = PSY_STIMULUS_CLASS(klass);
stimulus_class->play = visual_stimulus_play;
stimulus_class->set_duration = visual_stimulus_set_duration;
klass->update = visual_stimulus_update;
// The other properties are omitted.
/**
* PsyVisualStimulus:color
*
* The color `PsyColor` used to fill this object with
*/
visual_stimulus_properties[PROP_COLOR]
= g_param_spec_object("color",
"Color",
"The fill color for this stimulus",
PSY_TYPE_COLOR,
G_PARAM_READWRITE);
g_object_class_install_properties(
object_class, NUM_PROPERTIES, visual_stimulus_properties);
}
/**
* psy_visual_stimulus_get_color:
* @self: An instance of `PsyVisualStimulus`
*
* Get the color of the stimulus.
*
* Returns:(transfer none): the `PsyColor` of used to fill the stimuli
*/
PsyColor *
psy_visual_stimulus_get_color(PsyVisualStimulus *self)
{
PsyVisualStimulusPrivate *priv
= psy_visual_stimulus_get_instance_private(self);
g_return_val_if_fail(PSY_IS_VISUAL_STIMULUS(self), NULL);
return priv->color;
}
/**
* psy_visual_stimulus_set_color:
* @self: an instance of `PsyVisualStimulus`
* @color:(transfer none): An instance of `PsyVisualStimulus` that is going to
* be used in order to fill the shape of the stimulus
*
* Set the fill color of the stimulus, this color is used to fill the stimulus
*/
void
psy_visual_stimulus_set_color(PsyVisualStimulus *self, PsyColor *color)
{
PsyVisualStimulusPrivate *priv
= psy_visual_stimulus_get_instance_private(self);
g_return_if_fail(PSY_IS_VISUAL_STIMULUS(self) && PSY_IS_COLOR(color));
g_clear_object(&priv->color);
// psy_color_dup creates a deep copy, it calls g_object_new(PSY_TYPE_CIRCLE, NULL) and
// It does not copy by increasing the ref count. Perhaps this is wrong.
// PsyColor Might be converted to a boxed type as is is quite shallow anyway.
// but that is another issue.
priv->color = psy_color_dup(color);
}
The warning seems to come from: https://gitlab.gnome.org/GNOME/pygobject/-/blob/3.16.1/gi/pygi-object.c line 111
I would really like to fix this warning, so do I perhaps have errors in the implementation of my GObject derived class? Is there some issue with the GObject annotations? I would really appreciate any help I can get.