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

cxx-qt-lib: Add binding for QQmlApplicationEngine::singletonInstance #1140

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

redstrate
Copy link
Contributor

This allows accessing QObject singleton instances registered in the QML engine (using #[qml_singleton]) from the Rust side.

void*
qqmlapplicationengineSingletonInstance(QQmlApplicationEngine& engine, QAnyStringView uri, QAnyStringView typeName)
{
return reinterpret_cast<void*>(engine.singletonInstance<QObject*>(uri, typeName));
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 kind of hate everything about this, but this somehow works. Suggestions appreciated, since I don't know of any other method supported in cxx-qt that has a template parameter.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, once we have up/down casting and QObject already as a type #562 then this would be much easier. We would like this have this implemented in this cycle as it seems useful in multiple places.

It would be return a pointer to a QObject, then use the downcast_ptr method to get your original type back.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hm, right, this could be a decent way to solve this issue 🤔

@redstrate redstrate force-pushed the work/josh/add-singletoninstance-binding branch from 02a5168 to 3a86460 Compare December 10, 2024 01:56
@redstrate redstrate self-assigned this Dec 10, 2024
@redstrate redstrate force-pushed the work/josh/add-singletoninstance-binding branch from 3a86460 to 1f6d532 Compare December 10, 2024 01:57
This allows accessing QObject singleton instances registered in the QML
engine (using #[qml_singleton]) from the Rust side.
@redstrate redstrate force-pushed the work/josh/add-singletoninstance-binding branch from 1f6d532 to 55647b9 Compare December 10, 2024 01:58
Copy link

codecov bot commented Dec 10, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (f5afefe) to head (55647b9).

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #1140   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           71        71           
  Lines        11967     11967           
=========================================
  Hits         11967     11967           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@@ -79,13 +82,33 @@ mod ffi {
) -> Pin<&mut QQmlEngine>;
}

#[cfg(any(cxxqt_qt_version_at_least_7, cxxqt_qt_version_at_least_6_5))]
unsafe extern "C++" {
Copy link
Contributor Author

@redstrate redstrate Dec 10, 2024

Choose a reason for hiding this comment

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

This #[cfg] doesn't work I guess, so Qt5 and <6.5 builds will fail. A possible solution could be to separate this into a different cxx::bridge as suggested in this issue but that seems extreme?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Fully support for cfg flags everywhere comes in #1133 and #1132 which i plan to look at next.

For now you could try instead of putting the cfg flag on the extern block itself put it on the method as this is passed through to CXX.

if ptr.is_null() {
return None;
}
Some(&mut *(ptr as *mut T))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hm, this is definitely not safe to do here!

As you could basically "spoof" T to be different from the actual type that the singleton has.

In the most extreme case, I could just do:

engine.singleton_instance::<i32>(...);

We could mark the function itself as unsafe to move that responsibility up one level, but it's still not great...

@akiselev
Copy link
Contributor

akiselev commented Dec 19, 2024

For what it's worth, I implemented this using a template helper:

template <typename T>
T *qqmlengineSingletonInstance(QQmlEngine &engine, const QString &module, const QString &name)
{
    auto ptr =
        engine.template singletonInstance<T *>(
            QAnyStringView(module), QAnyStringView(name));
    return ptr;
}

Then in the C++ bridge:

    #[rust_name = "get_theme_singleton"]
    unsafe fn qqmlengineSingletonInstance(
        engine: Pin<&mut QQmlEngine>,
        module: &QString,
        name: &QString,
    ) -> *mut ThemeSingleton;

and finally the safe Rust wrapper:

    fn get_theme_singleton(self: Pin<&mut Self>) -> Option<Pin<&mut ThemeSingleton>> {
        unsafe {
            // This is safe because we're checking if it's null and only handing out a pinned mut ref
            // Qt does its own type safety checks here too
            let ptr = get_theme_singleton(
                self.as_qqmlengine()
                &QString::from("App.Core"),
                &QString::from("Theme"),
            );
            ptr.as_mut().map(|p| Pin::new_unchecked(p))
        }
    }

Then wrap it in a pair of traits:

pub trait GetSingleton<T> {
    fn try_singleton(self: Pin<&mut Self>) -> Option<Pin<&mut T>>;

    fn get_singleton(self: Pin<&mut Self>) -> Pin<&mut T> {
        self.try_singleton().expect("Singleton is not initialized")
    }
}

pub trait SingletonType {
    unsafe fn get_instance(engine: Pin<&mut QQmlEngine>) -> *mut Self;
}

impl<T: SingletonType> GetSingleton<T> for QQmlEngine {
    fn try_singleton(self: Pin<&mut Self>) -> Option<Pin<&mut T>> {
        unsafe {
            // This is safe because we're checking if it's null and only handing out a pinned mut ref
            // Qt does its own type safety checks here too
            let ptr = T::get_instance(self);
            ptr.as_mut().map(|p| Pin::new_unchecked(p))
        }
    }
}

impl SingletonType for ThemeSingleton {
    unsafe fn get_instance(engine: Pin<&mut QQmlEngine>) -> *mut Self {
        ffi::get_theme_instance(
            engine,
            &QString::from("App.Core"),
            &QString::from("Theme"),
        )
    }
}

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.

4 participants