Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

glib: Decouple ObjectInterface impl from interface class struct #1384

Merged
merged 1 commit into from
May 8, 2024

Conversation

felinira
Copy link
Contributor

@felinira felinira commented May 4, 2024

This makes ObjectInterface and ObjectSubclass more similar to each other and allows for cleaner separation between the unsafe ffi code and rust implementation.

There is some bikeshedding left to be done: InterfaceClassStruct is quite clumsy, but reusing ClassStruct is not possible, as it is tailored towards object subclasses. We could call it InterfaceStruct to align it with IsClass ->IsInterface and similar traits that omit Class. Then we should also name ObjectInterface::Class ObjectInterface::Interface I suppose.

Do we need something like glib::subclass::basic::ClassStruct for InterfaceClassStruct? I would say we don't, as most interfaces define vfuncs anyway.

Closes #1382

@sdroege
Copy link
Member

sdroege commented May 4, 2024

InterfaceClassStruct is quite clumsy, but reusing ClassStruct is not possible, as it is tailored towards object subclasses. We could call it InterfaceStruct to align it with IsClass ->IsInterface and similar traits that omit Class. Then we should also name ObjectInterface::Class ObjectInterface::Interface I suppose.

Yes I think that makes sense, both. Having the word class in the context of interfaces is just confusing, even if some GObject API calls it like that (g_type_class_peek() etc)

Do we need something like glib::subclass::basic::ClassStruct for InterfaceClassStruct? I would say we don't, as most interfaces define vfuncs anyway.

I think that would be nice, especially as it's just a bit of copy&paste. It is probably not useful often, but if you do a signals/properties-only interface then this would make it a bit less code to write (there are some such interfaces!).

@@ -1317,6 +1317,15 @@ macro_rules! glib_object_wrapper {
);
};

(@object_interface [$($attr:meta)*] $visibility:vis $name:ident $(<$($generic:ident $(: $bound:tt $(+ $bound2:tt)*)?),+>)?, $iface:ty,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's probably one branch of the macro that is not in use anymore now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. There are three branch arms from top to bottom:

The short @interface one is called by

// Interface
(
$(#[$attr:meta])*
$visibility:vis struct $name:ident $(<$($generic:ident $(: $bound:tt $(+ $bound2:tt)*)?),+>)? (Interface<$ffi_name:ty $(, $ffi_class_name:ty)?>) $(@requires $($requires:path),+)?;
match fn {
type_ => || $get_type_expr:expr,
}
) => {
$crate::glib_object_wrapper!(
@interface [$($attr)*] $visibility $name $(<$($generic $(: $bound $(+ $bound2)*)?),+>)?, *mut std::os::raw::c_void, $ffi_name,
$( @ffi_class $ffi_class_name ,)?
@type_ $get_type_expr,
@requires [$( $($requires),+ )?]
);
};

The new @object_interface one is called by

// ObjectInterface
(
$(#[$attr:meta])*
$visibility:vis struct $name:ident $(<$($generic:ident $(: $bound:tt $(+ $bound2:tt)*)?),+>)? (ObjectInterface<$iface_name:ty>) $(@requires $($requires:path),+)?;
) => {
$crate::glib_object_wrapper!(
@object_interface [$($attr)*] $visibility $name $(<$($generic $(: $bound $(+ $bound2)*)?),+>)?, $iface_name,
@type_ $crate::translate::IntoGlib::into_glib(<$iface_name as $crate::subclass::interface::ObjectInterfaceType>::type_()),
@requires [$( $($requires),+ )?]
);
};

The full implementation is being subsequently used be the former two macros. Also empirically, deleting either of the branches causes errors somewhere. 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mh ok! This macro grew too much :)

@@ -305,22 +323,22 @@ unsafe extern "C" fn interface_init<T: ObjectSubclass, A: IsImplementable<T>>(
) where
<A as ObjectType>::GlibClassType: Copy,
{
let iface = &mut *(iface as *mut crate::Interface<A>);
let klass = &mut *(iface as *mut crate::Interface<A>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOC, could we use g_type_class_peek_parent() here instead of making our own copy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since derived classes hold a reference count on their parent classes as long as they are instantiated, the returned class will always exist.

Always is a very strong assertion, and we do the same for class_init, so I don't see why not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah but peek_parent isn't right.

This code copies the type data of the interface into the type data of the class that implements the interface. https://docs.gtk.org/gobject/concepts.html#interface-initialization

We get a pointer to the interface class type in this method. It will be valid as long as instances of the interface exist. So we should be able to just take this pointer and use it in the TypeData.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is our version of the interface struct, with the changes applied by our type. Not the default/parent one, which is currently saved away

Copy link
Member

@sdroege sdroege left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me but I only took a short look now :) I'll check again tomorrow or Monday.

@felinira felinira force-pushed the object-interface-struct branch 2 times, most recently from 1eacb86 to a431d87 Compare May 4, 2024 20:56
@felinira
Copy link
Contributor Author

felinira commented May 4, 2024

Do we need something like glib::subclass::basic::ClassStruct for InterfaceClassStruct? I would say we don't, as most interfaces define vfuncs anyway.

I think that would be nice, especially as it's just a bit of copy&paste. It is probably not useful often, but if you do a signals/properties-only interface then this would make it a bit less code to write (there are some such interfaces!).

Alright, that apparently also needs an addition to ObjectInterfaceType then to define a GlibType and GlibClassType?

@sdroege
Copy link
Member

sdroege commented May 4, 2024

Alright, that apparently also needs an addition to ObjectInterfaceType then to define a GlibType and GlibClassType?

Also a good thing to make more consistent between interfaces and classes I guess :) GlibInterfaceType though.

For classes I used those associated types elsewhere in the past too so it might also become useful for something else.

@felinira
Copy link
Contributor Author

felinira commented May 6, 2024

Maybe it makes sense to handle this separately. Interfaces can also inherit from other interfaces, so we can't just assume GTypeInterface. We'd also need access to the public type, so I guess ObjectInterface also needs a Type and ParentType. This is all a bit much to add in one go IMO.

This makes ObjectInterface and ObjectSubclass more similar to each other
and allows for cleaner separation between unsafe ffi code and application
implementation.
@sdroege
Copy link
Member

sdroege commented May 6, 2024

Interfaces can also inherit from other interfaces, so we can't just assume GTypeInterface

Is that so? 🤯 I thought that worked differently via the prerequisites.

I'm fine with keeping this for a separate PR in any case. Don't have to do everything at once.

@felinira
Copy link
Contributor Author

felinira commented May 6, 2024

Is that so? 🤯 I thought that worked differently via the prerequisites.

You are right. I stared at GType for too long.

@sdroege
Copy link
Member

sdroege commented May 6, 2024

But now I'm wondering if that could actually be made to work 🤣

@sdroege
Copy link
Member

sdroege commented May 8, 2024

@felinira Should we get this in then or do you want to change more as part of this PR? The boxed copy of the struct and the simple generic interface struct can also be done separately, don't really have to entangle it all and this here is IMHO complete and good as it is.

@felinira
Copy link
Contributor Author

felinira commented May 8, 2024

@felinira Should we get this in then or do you want to change more as part of this PR? The boxed copy of the struct and the simple generic interface struct can also be done separately, don't really have to entangle it all and this here is IMHO complete and good as it is.

I won't mind getting this in now. I opened #1389 to track the remaining bits.

@sdroege sdroege merged commit 839fc12 into gtk-rs:master May 8, 2024
48 checks passed
@sdroege
Copy link
Member

sdroege commented May 8, 2024

Let's do that then :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Align ObjectInterface to ObjectSubclass
2 participants