Lua: Make sure stored objects live on the main thread

sol comes with "main_" variants of all reference types that
automatically make sure that they are linked to the main thread and
therefore don't die when a thread ends.

Change-Id: I22554a706d9b4881372f9aa25d4f0c78f8c079cc
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2024-11-04 18:38:19 +01:00
parent e0bb7b839b
commit 94aac9f446
5 changed files with 51 additions and 68 deletions

View File

@@ -69,7 +69,7 @@ void setupActionModule()
if (key == "context") if (key == "context")
b.setContext(Id::fromString(v.as<QString>())); b.setContext(Id::fromString(v.as<QString>()));
else if (key == "onTrigger") else if (key == "onTrigger")
b.addOnTriggered([f = v.as<sol::function>()]() { b.addOnTriggered([f = v.as<sol::main_function>()]() {
auto res = void_safe_call(f); auto res = void_safe_call(f);
QTC_CHECK_EXPECTED(res); QTC_CHECK_EXPECTED(res);
}); });

View File

@@ -177,14 +177,15 @@ void setProperties(std::unique_ptr<T> &item, const sol::table &children, QObject
} }
if constexpr (has_onReturnPressed<T>) { if constexpr (has_onReturnPressed<T>) {
const auto callback = children.get<sol::optional<sol::function>>("onReturnPressed"); const auto callback = children.get<sol::optional<sol::main_function>>("onReturnPressed");
if (callback) { if (callback) {
item->onReturnPressed([func = *callback]() { void_safe_call(func); }, guard); item->onReturnPressed([func = *callback]() { void_safe_call(func); }, guard);
} }
} }
if constexpr (has_onRightSideIconClicked<T>) { if constexpr (has_onRightSideIconClicked<T>) {
const auto callback = children.get<sol::optional<sol::function>>("onRightSideIconClicked"); const auto callback = children.get<sol::optional<sol::main_function>>(
"onRightSideIconClicked");
if (callback) if (callback)
item->onRightSideIconClicked([func = *callback]() { void_safe_call(func); }, guard); item->onRightSideIconClicked([func = *callback]() { void_safe_call(func); }, guard);
} }

View File

@@ -143,7 +143,7 @@ void setupProjectModule()
}); });
// startupProjectChanged // startupProjectChanged
registerHook("projects.startupProjectChanged", [](sol::function func, QObject *guard) { registerHook("projects.startupProjectChanged", [](sol::main_function func, QObject *guard) {
QObject::connect( QObject::connect(
ProjectManager::instance(), ProjectManager::instance(),
&ProjectManager::startupProjectChanged, &ProjectManager::startupProjectChanged,
@@ -155,7 +155,7 @@ void setupProjectModule()
}); });
// projectAdded // projectAdded
registerHook("projects.projectAdded", [](sol::function func, QObject *guard) { registerHook("projects.projectAdded", [](sol::main_function func, QObject *guard) {
QObject::connect( QObject::connect(
ProjectManager::instance(), ProjectManager::instance(),
&ProjectManager::projectAdded, &ProjectManager::projectAdded,
@@ -167,7 +167,7 @@ void setupProjectModule()
}); });
// projectRemoved // projectRemoved
registerHook("projects.projectRemoved", [](sol::function func, QObject *guard) { registerHook("projects.projectRemoved", [](sol::main_function func, QObject *guard) {
QObject::connect( QObject::connect(
ProjectManager::instance(), ProjectManager::instance(),
&ProjectManager::projectRemoved, &ProjectManager::projectRemoved,
@@ -179,7 +179,7 @@ void setupProjectModule()
}); });
// aboutToRemoveProject // aboutToRemoveProject
registerHook("projects.aboutToRemoveProject", [](sol::function func, QObject *guard) { registerHook("projects.aboutToRemoveProject", [](sol::main_function func, QObject *guard) {
QObject::connect( QObject::connect(
ProjectManager::instance(), ProjectManager::instance(),
&ProjectManager::aboutToRemoveProject, &ProjectManager::aboutToRemoveProject,
@@ -191,7 +191,7 @@ void setupProjectModule()
}); });
// runActionsUpdated // runActionsUpdated
registerHook("projects.runActionsUpdated", [](sol::function func, QObject *guard) { registerHook("projects.runActionsUpdated", [](sol::main_function func, QObject *guard) {
QObject::connect( QObject::connect(
ProjectExplorerPlugin::instance(), ProjectExplorerPlugin::instance(),
&ProjectExplorerPlugin::runActionsUpdated, &ProjectExplorerPlugin::runActionsUpdated,

View File

@@ -20,9 +20,7 @@ namespace Lua::Internal {
class LuaAspectContainer : public AspectContainer class LuaAspectContainer : public AspectContainer
{ {
public: public:
LuaAspectContainer(sol::state_view lua) LuaAspectContainer() {}
: m_mainThreadState(lua)
{}
sol::object dynamic_get(const std::string &key) sol::object dynamic_get(const std::string &key)
{ {
@@ -33,17 +31,8 @@ public:
return it->second; return it->second;
} }
void dynamic_set(const std::string &key, sol::stack_object value) void dynamic_set(const std::string &key, sol::main_object value)
{ {
if (value.lua_state() != m_mainThreadState.lua_state()) {
// This pushes the reference onto the other Lua State and returns a stack reference
// to it. We need to do this so a stack_object from some coroutine does not die
// while we store it.
sol::stack_reference newRef = sol::stack_reference(m_mainThreadState, value);
dynamic_set(key, newRef);
return;
}
if (!value.is<BaseAspect>()) if (!value.is<BaseAspect>())
throw std::runtime_error("AspectContainer can only contain BaseAspect instances"); throw std::runtime_error("AspectContainer can only contain BaseAspect instances");
@@ -63,14 +52,11 @@ public:
public: public:
std::unordered_map<std::string, sol::object> m_entries; std::unordered_map<std::string, sol::object> m_entries;
// This is the Lua state of the main thread.
sol::state_view m_mainThreadState;
}; };
std::unique_ptr<LuaAspectContainer> aspectContainerCreate( std::unique_ptr<LuaAspectContainer> aspectContainerCreate(const sol::main_table &options)
sol::state_view mainThreadState, const sol::table &options)
{ {
auto container = std::make_unique<LuaAspectContainer>(mainThreadState); auto container = std::make_unique<LuaAspectContainer>();
for (const auto &[k, v] : options) { for (const auto &[k, v] : options) {
if (k.is<std::string>()) { if (k.is<std::string>()) {
@@ -80,7 +66,7 @@ std::unique_ptr<LuaAspectContainer> aspectContainerCreate(
} else if (key == "layouter") { } else if (key == "layouter") {
if (v.is<sol::function>()) if (v.is<sol::function>())
container->setLayouter( container->setLayouter(
[func = v.as<sol::function>()]() -> Layouting::Layout { [func = v.as<sol::main_function>()]() -> Layouting::Layout {
auto res = safe_call<Layouting::Layout>(func); auto res = safe_call<Layouting::Layout>(func);
QTC_ASSERT_EXPECTED(res, return {}); QTC_ASSERT_EXPECTED(res, return {});
return *res; return *res;
@@ -90,19 +76,12 @@ std::unique_ptr<LuaAspectContainer> aspectContainerCreate(
container.get(), container.get(),
&AspectContainer::applied, &AspectContainer::applied,
container.get(), container.get(),
[func = v.as<sol::function>()] { void_safe_call(func); }); [func = v.as<sol::main_function>()] { void_safe_call(func); });
} else if (key == "settingsGroup") { } else if (key == "settingsGroup") {
container->setSettingsGroup(v.as<QString>()); container->setSettingsGroup(v.as<QString>());
} else { } else {
if (v.is<BaseAspect>()) { if (v.is<BaseAspect>()) {
// Dynamic_set needs a stack reference, so we push the object back on the stack ... container->dynamic_set(key, v);
v.push();
// Take a reference to the head of the stack ...
sol::stack_reference so(v.lua_state(), -1);
// call dynamic_set with the reference ...
container->dynamic_set(key, so);
// and lastly pop the object from the stack again.
v.pop();
} else { } else {
qWarning() << "Unknown key:" << key.c_str(); qWarning() << "Unknown key:" << key.c_str();
} }
@@ -126,14 +105,16 @@ void baseAspectCreate(BaseAspect *aspect, const std::string &key, const sol::obj
else if (key == "toolTip") else if (key == "toolTip")
aspect->setToolTip(value.as<QString>()); aspect->setToolTip(value.as<QString>());
else if (key == "onValueChanged") { else if (key == "onValueChanged") {
QObject::connect(aspect, &BaseAspect::changed, aspect, [func = value.as<sol::function>()]() { QObject::connect(
void_safe_call(func); aspect, &BaseAspect::changed, aspect, [func = value.as<sol::main_function>()]() {
}); void_safe_call(func);
});
} else if (key == "onVolatileValueChanged") { } else if (key == "onVolatileValueChanged") {
QObject::connect(aspect, QObject::connect(
&BaseAspect::volatileValueChanged, aspect,
aspect, &BaseAspect::volatileValueChanged,
[func = value.as<sol::function>()] { void_safe_call(func); }); aspect,
[func = value.as<sol::main_function>()] { void_safe_call(func); });
} else if (key == "enabler") } else if (key == "enabler")
aspect->setEnabler(value.as<BoolAspect *>()); aspect->setEnabler(value.as<BoolAspect *>());
else if (key == "macroExpander") { else if (key == "macroExpander") {
@@ -164,17 +145,17 @@ void typedAspectCreate(StringAspect *aspect, const std::string &key, const sol::
else if (key == "historyId") else if (key == "historyId")
aspect->setHistoryCompleter(value.as<QString>().toLocal8Bit()); aspect->setHistoryCompleter(value.as<QString>().toLocal8Bit());
else if (key == "valueAcceptor") else if (key == "valueAcceptor")
aspect->setValueAcceptor([func = value.as<sol::function>()](const QString &oldValue, aspect->setValueAcceptor(
const QString &newValue) [func = value.as<sol::main_function>()](const QString &oldValue, const QString &newValue)
-> std::optional<QString> { -> std::optional<QString> {
auto res = safe_call<std::optional<QString>>(func, oldValue, newValue); auto res = safe_call<std::optional<QString>>(func, oldValue, newValue);
QTC_ASSERT_EXPECTED(res, return std::nullopt); QTC_ASSERT_EXPECTED(res, return std::nullopt);
return *res; return *res;
}); });
else if (key == "showToolTipOnLabel") else if (key == "showToolTipOnLabel")
aspect->setShowToolTipOnLabel(value.as<bool>()); aspect->setShowToolTipOnLabel(value.as<bool>());
else if (key == "displayFilter") else if (key == "displayFilter")
aspect->setDisplayFilter([func = value.as<sol::function>()](const QString &value) { aspect->setDisplayFilter([func = value.as<sol::main_function>()](const QString &value) {
auto res = safe_call<QString>(func, value); auto res = safe_call<QString>(func, value);
QTC_ASSERT_EXPECTED(res, return value); QTC_ASSERT_EXPECTED(res, return value);
return *res; return *res;
@@ -194,7 +175,7 @@ void typedAspectCreate(StringAspect *aspect, const std::string &key, const sol::
else if (key == "completer") else if (key == "completer")
aspect->setCompleter(value.as<QCompleter*>()); aspect->setCompleter(value.as<QCompleter*>());
else if (key == "addOnRightSideIconClicked") { else if (key == "addOnRightSideIconClicked") {
aspect->addOnRightSideIconClicked(aspect, [func = value.as<sol::function>()]() { aspect->addOnRightSideIconClicked(aspect, [func = value.as<sol::main_function>()]() {
void_safe_call(func); void_safe_call(func);
}); });
} }
@@ -220,7 +201,7 @@ void typedAspectCreate(FilePathAspect *aspect, const std::string &key, const sol
else if (key == "validatePlaceHolder") else if (key == "validatePlaceHolder")
aspect->setValidatePlaceHolder(value.as<bool>()); aspect->setValidatePlaceHolder(value.as<bool>());
else if (key == "openTerminalHandler") else if (key == "openTerminalHandler")
aspect->setOpenTerminalHandler([func = value.as<sol::function>()]() { aspect->setOpenTerminalHandler([func = value.as<sol::main_function>()]() {
auto res = void_safe_call(func); auto res = void_safe_call(func);
QTC_CHECK_EXPECTED(res); QTC_CHECK_EXPECTED(res);
}); });
@@ -231,13 +212,13 @@ void typedAspectCreate(FilePathAspect *aspect, const std::string &key, const sol
else if (key == "baseFileName") else if (key == "baseFileName")
aspect->setBaseFileName(value.as<FilePath>()); aspect->setBaseFileName(value.as<FilePath>());
else if (key == "valueAcceptor") else if (key == "valueAcceptor")
aspect->setValueAcceptor([func = value.as<sol::function>()](const QString &oldValue, aspect->setValueAcceptor(
const QString &newValue) [func = value.as<sol::main_function>()](const QString &oldValue, const QString &newValue)
-> std::optional<QString> { -> std::optional<QString> {
auto res = safe_call<std::optional<QString>>(func, oldValue, newValue); auto res = safe_call<std::optional<QString>>(func, oldValue, newValue);
QTC_ASSERT_EXPECTED(res, return std::nullopt); QTC_ASSERT_EXPECTED(res, return std::nullopt);
return *res; return *res;
}); });
else if (key == "showToolTipOnLabel") else if (key == "showToolTipOnLabel")
aspect->setShowToolTipOnLabel(value.as<bool>()); aspect->setShowToolTipOnLabel(value.as<bool>());
else if (key == "autoApplyOnEditingFinished") else if (key == "autoApplyOnEditingFinished")
@@ -249,7 +230,7 @@ void typedAspectCreate(FilePathAspect *aspect, const std::string &key, const sol
}); });
*/ */
else if (key == "displayFilter") else if (key == "displayFilter")
aspect->setDisplayFilter([func = value.as<sol::function>()](const QString &path) { aspect->setDisplayFilter([func = value.as<sol::main_function>()](const QString &path) {
auto res = safe_call<QString>(func, path); auto res = safe_call<QString>(func, path);
QTC_ASSERT_EXPECTED(res, return path); QTC_ASSERT_EXPECTED(res, return path);
return *res; return *res;
@@ -355,7 +336,7 @@ void setupSettingsModule()
settings.new_usertype<LuaAspectContainer>( settings.new_usertype<LuaAspectContainer>(
"AspectContainer", "AspectContainer",
"create", "create",
[lua](const sol::table &options) { return aspectContainerCreate(lua, options); }, &aspectContainerCreate,
"apply", "apply",
&LuaAspectContainer::apply, &LuaAspectContainer::apply,
sol::meta_function::index, sol::meta_function::index,
@@ -612,19 +593,20 @@ void setupSettingsModule()
[](AspectList *aspect, const std::string &key, const sol::object &value) { [](AspectList *aspect, const std::string &key, const sol::object &value) {
if (key == "createItemFunction") { if (key == "createItemFunction") {
aspect->setCreateItemFunction( aspect->setCreateItemFunction(
[func = value.as<sol::function>()]() -> std::shared_ptr<BaseAspect> { [func = value.as<sol::main_function>()]()
-> std::shared_ptr<BaseAspect> {
auto res = safe_call<std::shared_ptr<BaseAspect>>(func); auto res = safe_call<std::shared_ptr<BaseAspect>>(func);
QTC_ASSERT_EXPECTED(res, return nullptr); QTC_ASSERT_EXPECTED(res, return nullptr);
return *res; return *res;
}); });
} else if (key == "onItemAdded") { } else if (key == "onItemAdded") {
aspect->setItemAddedCallback([func = value.as<sol::function>()]( aspect->setItemAddedCallback([func = value.as<sol::main_function>()](
std::shared_ptr<BaseAspect> item) { std::shared_ptr<BaseAspect> item) {
auto res = void_safe_call(func, item); auto res = void_safe_call(func, item);
QTC_CHECK_EXPECTED(res); QTC_CHECK_EXPECTED(res);
}); });
} else if (key == "onItemRemoved") { } else if (key == "onItemRemoved") {
aspect->setItemRemovedCallback([func = value.as<sol::function>()]( aspect->setItemRemovedCallback([func = value.as<sol::main_function>()](
std::shared_ptr<BaseAspect> item) { std::shared_ptr<BaseAspect> item) {
auto res = void_safe_call(func, item); auto res = void_safe_call(func, item);
QTC_CHECK_EXPECTED(res); QTC_CHECK_EXPECTED(res);

View File

@@ -392,7 +392,7 @@ void setupTextEditorModule()
return result; return result;
}); });
registerHook("editors.text.currentChanged", [](sol::function func, QObject *guard) { registerHook("editors.text.currentChanged", [](sol::main_function func, QObject *guard) {
QObject::connect( QObject::connect(
TextEditorRegistry::instance(), TextEditorRegistry::instance(),
&TextEditorRegistry::currentEditorChanged, &TextEditorRegistry::currentEditorChanged,
@@ -403,7 +403,7 @@ void setupTextEditorModule()
}); });
}); });
registerHook("editors.text.contentsChanged", [](sol::function func, QObject *guard) { registerHook("editors.text.contentsChanged", [](sol::main_function func, QObject *guard) {
QObject::connect( QObject::connect(
TextEditorRegistry::instance(), TextEditorRegistry::instance(),
&TextEditorRegistry::documentContentsChanged, &TextEditorRegistry::documentContentsChanged,
@@ -415,7 +415,7 @@ void setupTextEditorModule()
}); });
}); });
registerHook("editors.text.cursorChanged", [](sol::function func, QObject *guard) { registerHook("editors.text.cursorChanged", [](sol::main_function func, QObject *guard) {
QObject::connect( QObject::connect(
TextEditorRegistry::instance(), TextEditorRegistry::instance(),
&TextEditorRegistry::currentCursorChanged, &TextEditorRegistry::currentCursorChanged,