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")
b.setContext(Id::fromString(v.as<QString>()));
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);
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>) {
const auto callback = children.get<sol::optional<sol::function>>("onReturnPressed");
const auto callback = children.get<sol::optional<sol::main_function>>("onReturnPressed");
if (callback) {
item->onReturnPressed([func = *callback]() { void_safe_call(func); }, guard);
}
}
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)
item->onRightSideIconClicked([func = *callback]() { void_safe_call(func); }, guard);
}

View File

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

View File

@@ -20,9 +20,7 @@ namespace Lua::Internal {
class LuaAspectContainer : public AspectContainer
{
public:
LuaAspectContainer(sol::state_view lua)
: m_mainThreadState(lua)
{}
LuaAspectContainer() {}
sol::object dynamic_get(const std::string &key)
{
@@ -33,17 +31,8 @@ public:
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>())
throw std::runtime_error("AspectContainer can only contain BaseAspect instances");
@@ -63,14 +52,11 @@ public:
public:
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(
sol::state_view mainThreadState, const sol::table &options)
std::unique_ptr<LuaAspectContainer> aspectContainerCreate(const sol::main_table &options)
{
auto container = std::make_unique<LuaAspectContainer>(mainThreadState);
auto container = std::make_unique<LuaAspectContainer>();
for (const auto &[k, v] : options) {
if (k.is<std::string>()) {
@@ -80,7 +66,7 @@ std::unique_ptr<LuaAspectContainer> aspectContainerCreate(
} else if (key == "layouter") {
if (v.is<sol::function>())
container->setLayouter(
[func = v.as<sol::function>()]() -> Layouting::Layout {
[func = v.as<sol::main_function>()]() -> Layouting::Layout {
auto res = safe_call<Layouting::Layout>(func);
QTC_ASSERT_EXPECTED(res, return {});
return *res;
@@ -90,19 +76,12 @@ std::unique_ptr<LuaAspectContainer> aspectContainerCreate(
container.get(),
&AspectContainer::applied,
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") {
container->setSettingsGroup(v.as<QString>());
} else {
if (v.is<BaseAspect>()) {
// Dynamic_set needs a stack reference, so we push the object back on the stack ...
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();
container->dynamic_set(key, v);
} else {
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")
aspect->setToolTip(value.as<QString>());
else if (key == "onValueChanged") {
QObject::connect(aspect, &BaseAspect::changed, aspect, [func = value.as<sol::function>()]() {
QObject::connect(
aspect, &BaseAspect::changed, aspect, [func = value.as<sol::main_function>()]() {
void_safe_call(func);
});
} else if (key == "onVolatileValueChanged") {
QObject::connect(aspect,
QObject::connect(
aspect,
&BaseAspect::volatileValueChanged,
aspect,
[func = value.as<sol::function>()] { void_safe_call(func); });
[func = value.as<sol::main_function>()] { void_safe_call(func); });
} else if (key == "enabler")
aspect->setEnabler(value.as<BoolAspect *>());
else if (key == "macroExpander") {
@@ -164,8 +145,8 @@ void typedAspectCreate(StringAspect *aspect, const std::string &key, const sol::
else if (key == "historyId")
aspect->setHistoryCompleter(value.as<QString>().toLocal8Bit());
else if (key == "valueAcceptor")
aspect->setValueAcceptor([func = value.as<sol::function>()](const QString &oldValue,
const QString &newValue)
aspect->setValueAcceptor(
[func = value.as<sol::main_function>()](const QString &oldValue, const QString &newValue)
-> std::optional<QString> {
auto res = safe_call<std::optional<QString>>(func, oldValue, newValue);
QTC_ASSERT_EXPECTED(res, return std::nullopt);
@@ -174,7 +155,7 @@ void typedAspectCreate(StringAspect *aspect, const std::string &key, const sol::
else if (key == "showToolTipOnLabel")
aspect->setShowToolTipOnLabel(value.as<bool>());
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);
QTC_ASSERT_EXPECTED(res, return value);
return *res;
@@ -194,7 +175,7 @@ void typedAspectCreate(StringAspect *aspect, const std::string &key, const sol::
else if (key == "completer")
aspect->setCompleter(value.as<QCompleter*>());
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);
});
}
@@ -220,7 +201,7 @@ void typedAspectCreate(FilePathAspect *aspect, const std::string &key, const sol
else if (key == "validatePlaceHolder")
aspect->setValidatePlaceHolder(value.as<bool>());
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);
QTC_CHECK_EXPECTED(res);
});
@@ -231,8 +212,8 @@ void typedAspectCreate(FilePathAspect *aspect, const std::string &key, const sol
else if (key == "baseFileName")
aspect->setBaseFileName(value.as<FilePath>());
else if (key == "valueAcceptor")
aspect->setValueAcceptor([func = value.as<sol::function>()](const QString &oldValue,
const QString &newValue)
aspect->setValueAcceptor(
[func = value.as<sol::main_function>()](const QString &oldValue, const QString &newValue)
-> std::optional<QString> {
auto res = safe_call<std::optional<QString>>(func, oldValue, newValue);
QTC_ASSERT_EXPECTED(res, return std::nullopt);
@@ -249,7 +230,7 @@ void typedAspectCreate(FilePathAspect *aspect, const std::string &key, const sol
});
*/
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);
QTC_ASSERT_EXPECTED(res, return path);
return *res;
@@ -355,7 +336,7 @@ void setupSettingsModule()
settings.new_usertype<LuaAspectContainer>(
"AspectContainer",
"create",
[lua](const sol::table &options) { return aspectContainerCreate(lua, options); },
&aspectContainerCreate,
"apply",
&LuaAspectContainer::apply,
sol::meta_function::index,
@@ -612,19 +593,20 @@ void setupSettingsModule()
[](AspectList *aspect, const std::string &key, const sol::object &value) {
if (key == "createItemFunction") {
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);
QTC_ASSERT_EXPECTED(res, return nullptr);
return *res;
});
} else if (key == "onItemAdded") {
aspect->setItemAddedCallback([func = value.as<sol::function>()](
aspect->setItemAddedCallback([func = value.as<sol::main_function>()](
std::shared_ptr<BaseAspect> item) {
auto res = void_safe_call(func, item);
QTC_CHECK_EXPECTED(res);
});
} else if (key == "onItemRemoved") {
aspect->setItemRemovedCallback([func = value.as<sol::function>()](
aspect->setItemRemovedCallback([func = value.as<sol::main_function>()](
std::shared_ptr<BaseAspect> item) {
auto res = void_safe_call(func, item);
QTC_CHECK_EXPECTED(res);

View File

@@ -392,7 +392,7 @@ void setupTextEditorModule()
return result;
});
registerHook("editors.text.currentChanged", [](sol::function func, QObject *guard) {
registerHook("editors.text.currentChanged", [](sol::main_function func, QObject *guard) {
QObject::connect(
TextEditorRegistry::instance(),
&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(
TextEditorRegistry::instance(),
&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(
TextEditorRegistry::instance(),
&TextEditorRegistry::currentCursorChanged,