Introduce timeout to await

This commit is contained in:
Martín Capello 2025-02-26 13:47:22 -03:00
parent bbf94cfab0
commit cc219d7e33
2 changed files with 81 additions and 54 deletions

View File

@ -360,65 +360,73 @@ void Dialog_connect_signal(lua_State* L,
int Dialog_new(lua_State* L)
{
return ui::await<int>([L]() -> int {
// If we don't have UI, just return nil
if (!App::instance()->isGui())
return 0;
try {
return ui::await<int>(
[L]() -> int {
// If we don't have UI, just return nil
if (!App::instance()->isGui())
return 0;
// Get the title and the type of window (with or without title bar)
ui::Window::Type windowType = ui::Window::WithTitleBar;
std::string title = "Script";
if (lua_isstring(L, 1)) {
title = lua_tostring(L, 1);
}
else if (lua_istable(L, 1)) {
int type = lua_getfield(L, 1, "title");
if (type != LUA_TNIL)
title = lua_tostring(L, -1);
lua_pop(L, 1);
// Get the title and the type of window (with or without title bar)
ui::Window::Type windowType = ui::Window::WithTitleBar;
std::string title = "Script";
if (lua_isstring(L, 1)) {
title = lua_tostring(L, 1);
}
else if (lua_istable(L, 1)) {
int type = lua_getfield(L, 1, "title");
if (type != LUA_TNIL)
title = lua_tostring(L, -1);
lua_pop(L, 1);
type = lua_getfield(L, 1, "notitlebar");
if (type != LUA_TNIL && lua_toboolean(L, -1))
windowType = ui::Window::WithoutTitleBar;
lua_pop(L, 1);
}
type = lua_getfield(L, 1, "notitlebar");
if (type != LUA_TNIL && lua_toboolean(L, -1))
windowType = ui::Window::WithoutTitleBar;
lua_pop(L, 1);
}
auto dlg = push_new<Dialog>(L, windowType, title);
auto dlg = push_new<Dialog>(L, windowType, title);
// The uservalue of the dialog userdata will contain a table that
// stores all the callbacks to handle events. As these callbacks can
// reference the dialog itself, it's important to store callbacks in
// this table that depends on the dialog lifetime itself
// (i.e. uservalue) and in the global registry, because in that case
// we could create a cyclic reference that would be not GC'd.
lua_newtable(L);
lua_setuservalue(L, -2);
// The uservalue of the dialog userdata will contain a table that
// stores all the callbacks to handle events. As these callbacks can
// reference the dialog itself, it's important to store callbacks in
// this table that depends on the dialog lifetime itself
// (i.e. uservalue) and in the global registry, because in that case
// we could create a cyclic reference that would be not GC'd.
lua_newtable(L);
lua_setuservalue(L, -2);
if (lua_istable(L, 1)) {
int type = lua_getfield(L, 1, "parent");
if (type != LUA_TNIL) {
if (auto parentDlg = may_get_obj<Dialog>(L, -1))
dlg->window.setParentDisplay(parentDlg->window.display());
}
lua_pop(L, 1);
if (lua_istable(L, 1)) {
int type = lua_getfield(L, 1, "parent");
if (type != LUA_TNIL) {
if (auto parentDlg = may_get_obj<Dialog>(L, -1))
dlg->window.setParentDisplay(parentDlg->window.display());
}
lua_pop(L, 1);
type = lua_getfield(L, 1, "onclose");
if (type == LUA_TFUNCTION) {
Dialog_connect_signal(L, -2, dlg->window.Close, [](lua_State*, CloseEvent&) {
// Do nothing
});
}
lua_pop(L, 1);
}
type = lua_getfield(L, 1, "onclose");
if (type == LUA_TFUNCTION) {
Dialog_connect_signal(L, -2, "onclose", dlg->window.Close, [](lua_State*, CloseEvent&) {
// Do nothing
});
}
lua_pop(L, 1);
}
// The showRef must be the last reference to the dialog to be
// unreferenced after the window is closed (that's why this is the
// last connection to ui::Window::Close)
dlg->unrefShowOnClose();
// The showRef must be the last reference to the dialog to be
// unreferenced after the window is closed (that's why this is the
// last connection to ui::Window::Close)
dlg->unrefShowOnClose();
TRACE_DIALOG("Dialog_new", dlg);
return 1;
});
TRACE_DIALOG("Dialog_new", dlg);
return 1;
},
500);
}
catch (AwaitTimeoutException& ex) {
luaL_error(L, "Could not create Dialog");
return 0;
}
}
int Dialog_gc(lua_State* L)
@ -1884,7 +1892,15 @@ int Dialog_set_bounds(lua_State* L)
}
#define wrap(func) \
[](lua_State* L) -> int { return ui::await<int>([L]() -> int { return func(L); }); }
[](lua_State* L) -> int { \
try { \
return ui::await<int>([L]() -> int { return func(L); }, 500); \
} \
catch (AwaitTimeoutException & ex) { \
luaL_error(L, "Could not execute %s", #func); \
return 0; \
} \
}
const luaL_Reg Dialog_methods[] = {
{ "__gc", wrap(Dialog_gc) },

View File

@ -7,7 +7,6 @@
#ifndef UI_AWAIT_H_INCLUDED
#define UI_AWAIT_H_INCLUDED
#include <type_traits>
#pragma once
#include "os/event.h"
@ -19,13 +18,18 @@
namespace ui {
class AwaitTimeoutException : std::runtime_error {
public:
AwaitTimeoutException() : std::runtime_error("Await timed out") {}
};
// Awaits for the future result of the function passed.
// It is meant to be used from background threads to ask something to the main
// thread and wait for its result.
// If await is called from the main thread it just executes the function
// and returns the result.
template<typename T>
T await(std::function<T()>&& func)
T await(std::function<T()>&& func, int timeout = 0)
{
if (ui::is_ui_thread())
return func();
@ -48,7 +52,14 @@ T await(std::function<T()>&& func)
}
});
os::queue_event(ev);
// Wait for the future
if (timeout > 0) {
auto status = future.wait_for(std::chrono::milliseconds(timeout));
if (status != std::future_status::ready) {
throw AwaitTimeoutException();
}
}
return future.get();
}