Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16.0)

project(JlQML)

set(JlQML_VERSION 0.9.0)
set(JlQML_VERSION 0.10.0)
message(STATUS "Project version: v${JlQML_VERSION}")

set(CMAKE_MACOSX_RPATH 1)
Expand Down Expand Up @@ -61,6 +61,8 @@ qt6_add_qml_module(jlqml
julia_display.cpp
julia_function.hpp
julia_function.cpp
julia_imageprovider.hpp
julia_imageprovider.cpp
julia_itemmodel.hpp
julia_itemmodel.cpp
julia_painteditem.hpp
Expand Down
14 changes: 12 additions & 2 deletions application_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ 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();

const int status = app->exec();
if (status != 0)
{
Expand All @@ -109,7 +119,7 @@ void ApplicationManager::add_import_path(std::string path)
m_import_paths.push_back(path);
}

ApplicationManager::ApplicationManager()
ApplicationManager::ApplicationManager() : m_event_hook(jl_get_function(m_qml_mod, "process_eventloop_updates"))
{
if(QProcessEnvironment::systemEnvironment().contains("QSG_RENDER_LOOP"))
{
Expand All @@ -136,6 +146,7 @@ void ApplicationManager::cleanup()
}
delete JuliaSingleton::s_singletonInstance;
JuliaSingleton::s_singletonInstance = nullptr;
ForeignThreadManager::instance().cleanup();
}

void ApplicationManager::check_no_engine()
Expand All @@ -156,7 +167,6 @@ void ApplicationManager::set_engine(QQmlEngine* e)
{
e->addImportPath(QString::fromStdString(path));
}
ForeignThreadManager::instance().clear(QThread::currentThread());
}

void ApplicationManager::process_events()
Expand Down
2 changes: 2 additions & 0 deletions application_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <QtQml>

#include "jlcxx/jlcxx.hpp"
#include "jlcxx/functions.hpp"

#include "julia_api.hpp"

Expand Down Expand Up @@ -59,6 +60,7 @@ class ApplicationManager
QQmlEngine* m_engine = nullptr;
QQmlContext* m_root_ctx = nullptr;
std::vector<std::string> m_import_paths;
jlcxx::JuliaFunction m_event_hook;
};

}
Expand Down
128 changes: 60 additions & 68 deletions foreign_thread_manager.cpp
Original file line number Diff line number Diff line change
@@ -1,103 +1,95 @@
#include <QQuickWindow>

#include "jlcxx/jlcxx.hpp"

#include "foreign_thread_manager.hpp"

ForeignThreadManager& ForeignThreadManager::instance()

namespace qmlwrap
{
static ForeignThreadManager instance;
return instance;
}

void ForeignThreadManager::add_thread(QThread *t)
QMutex ForeignThreadManager::m_juliamutex;
thread_local ForeignThreadManager* ForeignThreadManager::m_instance = nullptr;

ForeignThreadManager& ForeignThreadManager::instance()
{
m_mutex.lock();
if(!m_threads.contains(t))
if (m_instance == nullptr)
{
m_threads.insert(t);
#if (JULIA_VERSION_MAJOR * 100 + JULIA_VERSION_MINOR) >= 109
jl_adopt_thread();
#else
if(m_threads.size() > 1)
{
std::cout << "Warning: using multiple threads in Julia versions older than 1.9 will probably crash" << std::endl;
}
#endif
m_instance = new ForeignThreadManager();
}
m_mutex.unlock();
return *m_instance;
}

void ForeignThreadManager::clear(QThread* main_thread)
ForeignThreadManager::~ForeignThreadManager()
{
m_mutex.lock();
m_threads.clear();
m_threads.insert(main_thread);
m_mutex.unlock();
gc_safe_leave();
}

// This was defined after Julia 1.10
#ifndef JL_GC_STATE_UNSAFE
#define JL_GC_STATE_UNSAFE 0
#endif
ForeignThreadManager::ForeignThreadManager()
{
if (!QThread::isMainThread())
{
#if (JULIA_VERSION_MAJOR * 100 + JULIA_VERSION_MINOR) >= 109
jl_adopt_thread();
#endif
}
gc_safe_enter();
}

void _gc_safe_enter(int& state)
void ForeignThreadManager::gc_safe_enter()
{
jl_ptls_t ptls = jl_current_task->ptls;
state = jl_gc_safe_enter(ptls);
m_state = jl_gc_safe_enter(ptls);
#if (JULIA_VERSION_MAJOR * 100 + JULIA_VERSION_MINOR) > 111
ptls->engine_nqueued++;
#endif
}

void _gc_safe_leave(int state)
void ForeignThreadManager::gc_safe_leave()
{
jl_ptls_t ptls = jl_current_task->ptls;
jl_gc_safe_leave(ptls, state);
jl_gc_safe_leave(ptls, m_state);
#if (JULIA_VERSION_MAJOR * 100 + JULIA_VERSION_MINOR) > 111
ptls->engine_nqueued--;
#endif
}

void ForeignThreadManager::add_window(QQuickItem* item)
void ForeignThreadManager::begin_julia()
{
QObject::connect(item, &QQuickItem::windowChanged, [item, m_main_gc_state = JL_GC_STATE_UNSAFE, m_render_gc_state = JL_GC_STATE_UNSAFE] (QQuickWindow* w) mutable
if(m_depth == 0)
{
if (w == nullptr)
{
return;
}
item->connect(w, &QQuickWindow::sceneGraphInitialized, [] ()
{
// Adopt the render thread when it is first initialized
ForeignThreadManager::instance().add_thread(QThread::currentThread());
});
item->connect(w, &QQuickWindow::afterAnimating, [&m_main_gc_state] ()
{
// afterAnimating is the last signal sent before locking the main loop, so indicate
// that the main loop is in a safe state for the Julia GC to run
_gc_safe_enter(m_main_gc_state);
});
item->connect(w, &QQuickWindow::beforeRendering, w, [&m_main_gc_state] ()
{
// beforeRendering is the first signal sent after unlocking the main thread, so restore
// the Julia GC state here. Queued connection so this is executed on the main thread
_gc_safe_leave(m_main_gc_state);
}, Qt::QueuedConnection);
item->connect(w, &QQuickWindow::afterFrameEnd, w, [&m_render_gc_state] ()
{
// After the rendering is done, the render thread may block for event handling, so we mark it as in a GC safe state
// to prevent deadlock
_gc_safe_enter(m_render_gc_state);
}, Qt::DirectConnection);
item->connect(w, &QQuickWindow::beforeFrameBegin, w, [&m_render_gc_state] ()
{
// Reset GC state when the render thread might execute Julia code again
_gc_safe_leave(m_render_gc_state);
}, Qt::DirectConnection);
});
m_juliamutex.lock();
gc_safe_leave();
}
++m_depth;
}

ForeignThreadManager::ForeignThreadManager()
void ForeignThreadManager::end_julia()
{
--m_depth;
if(m_depth == 0)
{
gc_safe_enter();
m_juliamutex.unlock();
}
}

void ForeignThreadManager::cleanup()
{
if(!QThread::isMainThread())
{
return;
}
delete m_instance;
m_instance = nullptr;
}

GCGuard::GCGuard()
{
ForeignThreadManager::instance().begin_julia();
}

GCGuard::~GCGuard()
{
ForeignThreadManager::instance().end_julia();
}

}
31 changes: 26 additions & 5 deletions foreign_thread_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,38 @@
#include <QQuickItem>
#include <QThread>


namespace qmlwrap
{

class ForeignThreadManager
{
public:
static ForeignThreadManager& instance();
~ForeignThreadManager();

void gc_safe_enter();
void gc_safe_leave();

void begin_julia();
void end_julia();

void add_thread(QThread* t);
void clear(QThread* main_thread);
void add_window(QQuickItem* item);
// Remove the current instance, to be called after exec finishes.
void cleanup();

private:
ForeignThreadManager();
QSet<QThread*> m_threads;
QMutex m_mutex;

int m_state = 0;
int m_depth = 0;
static thread_local ForeignThreadManager* m_instance;
static QMutex m_juliamutex;
};

struct GCGuard
{
GCGuard();
~GCGuard();
};

}
6 changes: 4 additions & 2 deletions julia_canvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace qmlwrap

JuliaCanvas::JuliaCanvas(QQuickItem *parent) : QQuickPaintedItem(parent)
{
ForeignThreadManager::instance().add_window(this);
}

void JuliaCanvas::paint(QPainter *painter)
Expand All @@ -22,7 +21,10 @@ void JuliaCanvas::paint(QPainter *painter)
unsigned int *draw_buffer = new unsigned int[iwidth * iheight];

// call julia painter
m_callback(draw_buffer, iwidth, iheight);
{
GCGuard gc_guard;
m_callback(draw_buffer, iwidth, iheight);
}

// make QImage
QImage *image = new QImage((uchar*)draw_buffer, width(), height(), QImage::Format_ARGB32);
Expand Down
2 changes: 2 additions & 0 deletions julia_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "julia_function.hpp"
#include "jlqml.hpp"
#include "foreign_thread_manager.hpp"

namespace qmlwrap
{
Expand All @@ -20,6 +21,7 @@ JuliaFunction::~JuliaFunction()

QVariant JuliaFunction::call(const QVariantList& args)
{
GCGuard gc_guard;
using call_julia_func_t = void* (*) (jl_value_t*, const void*);
static call_julia_func_t call_func = reinterpret_cast<call_julia_func_t>(jlcxx::unbox<void*>(jlcxx::JuliaFunction(jl_get_function(m_qml_mod, "get_julia_call"))()));
QVariant result_var = *reinterpret_cast<QVariant*>(call_func(m_f, &args));
Expand Down
62 changes: 62 additions & 0 deletions julia_imageprovider.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "jlcxx/functions.hpp"

#include "julia_api.hpp"
#include "julia_imageprovider.hpp"
#include "foreign_thread_manager.hpp"
#include <QThread>

namespace qmlwrap
{

JuliaImageProvider::JuliaImageProvider(QQmlImageProviderBase::ImageType type) : QQuickImageProvider(type)
{
}

template<typename ImageT>
inline ImageT process_request(JuliaImageProvider::callback_t f, const QString &id, QSize *size, const QSize &requestedSize)
{
if(f == nullptr)
{
throw std::runtime_error("No callback function set for JuliaImageProvider");
}

GCGuard gc_guard;
ImageResult<ImageT> response = jlcxx::unbox<ImageResult<ImageT>>(f(id, requestedSize.width(), requestedSize.height()));
*size = response.m_size;
return std::move(response.m_image);
}

QImage JuliaImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
if(imageType() != QQmlImageProviderBase::Image)
{
throw std::runtime_error("JuliaImageProvider is not of type Image");
}
return process_request<QImage>(m_callback, id, size, requestedSize);
}

QPixmap JuliaImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
if(imageType() != QQmlImageProviderBase::Pixmap)
{
throw std::runtime_error("JuliaImageProvider is not of type Pixmap");
}
return process_request<QPixmap>(m_callback, id, size, requestedSize);
}

QQuickTextureFactory *JuliaImageProvider::requestTexture(const QString &id, QSize *size, const QSize &requestedSize)
{
if(imageType() != QQmlImageProviderBase::Texture)
{
throw std::runtime_error("JuliaImageProvider is not of type Texture");
}
return process_request<QQuickTextureFactory*>(m_callback, id, size, requestedSize);
}

void JuliaImageProvider::set_callback(jlcxx::SafeCFunction fdata)
{
m_callback = jlcxx::make_function_pointer<jl_value_t*(const QString&,int,int)>(fdata);
}

} // namespace qmlwrap

Loading
Loading