From b94f736edd2970b022cf628842ff2719559a7a4f Mon Sep 17 00:00:00 2001 From: Bart Janssens Date: Wed, 8 Apr 2026 20:45:57 +0200 Subject: [PATCH 1/2] Forgotten GC lock --- makie_viewport.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/makie_viewport.cpp b/makie_viewport.cpp index c77372a..6600672 100644 --- a/makie_viewport.cpp +++ b/makie_viewport.cpp @@ -136,6 +136,7 @@ void MakieViewport::setup_buffer(QOpenGLFramebufferObject* fbo) connect(window(), &QQuickWindow::sceneGraphInvalidated, [this] () { + GCGuard gc_guard; MakieSupport::instance().on_context_destroy(m_screen); }); } From 87ce6cbaecc1a0920e2c7b1ca249a517c113fef1 Mon Sep 17 00:00:00 2001 From: Bart Janssens Date: Wed, 8 Apr 2026 20:46:58 +0200 Subject: [PATCH 2/2] Fix excessive CPU usage for event loop updates from Julia --- application_manager.cpp | 29 ++++++++++++++++++++--------- application_manager.hpp | 16 +++++++++++++++- foreign_thread_manager.cpp | 6 ++++++ foreign_thread_manager.hpp | 2 ++ wrap_qml.cpp | 16 ++++++++++++++-- 5 files changed, 57 insertions(+), 12 deletions(-) diff --git a/application_manager.cpp b/application_manager.cpp index ef73476..ea51d6e 100644 --- a/application_manager.cpp +++ b/application_manager.cpp @@ -95,15 +95,9 @@ void ApplicationManager::exec() app->exit(status); }); - QTimer* main_timer = new QTimer(m_engine); - main_timer->setInterval(0); - QObject::connect(main_timer, &QTimer::timeout, [this] - { - GCGuard gc_guard; - m_event_hook(); - }); - main_timer->start(); + m_event_loop_updater = new EventLoopUpdater(m_engine, jl_get_function(m_qml_mod, "process_eventloop_updates")); + ForeignThreadManager::instance(); // Make sure the main thread is put in GC safe mode const int status = app->exec(); if (status != 0) { @@ -119,7 +113,13 @@ void ApplicationManager::add_import_path(std::string path) m_import_paths.push_back(path); } -ApplicationManager::ApplicationManager() : m_event_hook(jl_get_function(m_qml_mod, "process_eventloop_updates")) +void ApplicationManager::queue_process_eventloop_updates() +{ + assert(m_event_loop_updater != nullptr); + QMetaObject::invokeMethod(m_event_loop_updater, [this]{ m_event_loop_updater->process_eventloop_updates(); }, Qt::QueuedConnection); +} + +ApplicationManager::ApplicationManager() { if(QProcessEnvironment::systemEnvironment().contains("QSG_RENDER_LOOP")) { @@ -147,6 +147,7 @@ void ApplicationManager::cleanup() delete JuliaSingleton::s_singletonInstance; JuliaSingleton::s_singletonInstance = nullptr; ForeignThreadManager::instance().cleanup(); + m_event_loop_updater = nullptr; } void ApplicationManager::check_no_engine() @@ -177,4 +178,14 @@ void ApplicationManager::process_events() jl_module_t* ApplicationManager::m_qml_mod = nullptr; +EventLoopUpdater::EventLoopUpdater(QObject *parent, jl_value_t *f) : QObject(parent), m_process_eventloop_updates(f) +{ +} + +void EventLoopUpdater::process_eventloop_updates() +{ + GCGuard gc_guard; + m_process_eventloop_updates(); +} + } diff --git a/application_manager.hpp b/application_manager.hpp index 178ec7e..b669693 100644 --- a/application_manager.hpp +++ b/application_manager.hpp @@ -19,6 +19,18 @@ namespace qmlwrap { +/// Helper to invoke a Julia function inside the main event loop +class EventLoopUpdater : public QObject +{ + Q_OBJECT +public: + EventLoopUpdater(QObject* parent, jl_value_t* f); + void process_eventloop_updates(); + +private: + jlcxx::JuliaFunction m_process_eventloop_updates; +}; + /// Manage creation and destruction of the application and the QML engine, class ApplicationManager { @@ -45,6 +57,8 @@ class ApplicationManager void add_import_path(std::string path); + void queue_process_eventloop_updates(); + static void process_events(); static jl_module_t* m_qml_mod; @@ -60,7 +74,7 @@ class ApplicationManager QQmlEngine* m_engine = nullptr; QQmlContext* m_root_ctx = nullptr; std::vector m_import_paths; - jlcxx::JuliaFunction m_event_hook; + EventLoopUpdater* m_event_loop_updater = nullptr; }; } diff --git a/foreign_thread_manager.cpp b/foreign_thread_manager.cpp index e97b909..1e02a37 100644 --- a/foreign_thread_manager.cpp +++ b/foreign_thread_manager.cpp @@ -72,6 +72,12 @@ void ForeignThreadManager::end_julia() } } +void ForeignThreadManager::yield() +{ + end_julia(); + begin_julia(); +} + void ForeignThreadManager::cleanup() { if(!QThread::isMainThread()) diff --git a/foreign_thread_manager.hpp b/foreign_thread_manager.hpp index 2726519..ad7e1bf 100644 --- a/foreign_thread_manager.hpp +++ b/foreign_thread_manager.hpp @@ -19,6 +19,8 @@ class ForeignThreadManager void begin_julia(); void end_julia(); + void yield(); + // Remove the current instance, to be called after exec finishes. void cleanup(); diff --git a/wrap_qml.cpp b/wrap_qml.cpp index f978c5d..fddafd2 100644 --- a/wrap_qml.cpp +++ b/wrap_qml.cpp @@ -573,6 +573,7 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& qml_module) }); qml_module.add_type("QSize") + .constructor() .method("width", &QSize::width) .method("height", &QSize::height); @@ -780,10 +781,20 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& qml_module) qml_module.method("app_exec", []() { qmlwrap::ApplicationManager::instance().exec(); }); qml_module.method("process_events", qmlwrap::ApplicationManager::process_events); qml_module.method("add_import_path", [](std::string path) { qmlwrap::ApplicationManager::instance().add_import_path(path); }); + qml_module.method("queue_process_eventloop_updates", []() { qmlwrap::ApplicationManager::instance().queue_process_eventloop_updates(); }); + + qml_module.method("yield", []() { qmlwrap::ForeignThreadManager::instance().yield(); }); qml_module.add_type("QTimer", julia_base_type()) + .constructor() + .method("setInterval", static_cast(&QTimer::setInterval)) .method("start", [] (QTimer& t) { t.start(); } ) - .method("stop", &QTimer::stop); + .method("stop", &QTimer::stop) + .method("callOnTimeout", [] (QTimer& t, jl_value_t* julia_function) + { + JuliaFunction f(julia_function); + t.callOnTimeout([f] () { f(); }); + }); // Emit signals helper qml_module.method("emit", [](const char *signal_name, const QVariantList& args) { @@ -999,7 +1010,8 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& qml_module) .method("copy", static_cast(&QImage::copy)) .method("copy", [] (const QImage& i) { return i.copy(); } ) .method("height", &QImage::height) - .method("width", &QImage::width); + .method("width", &QImage::width) + .method("fill", static_cast(&QImage::fill)); qml_module.add_type("QPixmap", julia_base_type()) .constructor()