mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 13:21:34 +00:00
Introduce the concept of tasks in the engine
This commit is contained in:
parent
be6d2251aa
commit
bbf94cfab0
@ -73,7 +73,7 @@ void RunScriptCommand::onExecute(Context* context)
|
||||
return;
|
||||
}
|
||||
|
||||
App::instance()->scriptEngine()->evalUserFile(m_filename, m_params);
|
||||
App::instance()->scriptEngine()->evalUserFileInTask(m_filename, m_params);
|
||||
|
||||
if (context->isUIAvailable())
|
||||
ui::Manager::getDefault()->invalidate();
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "base/fs.h"
|
||||
#include "base/paths.h"
|
||||
#include "base/remove_from_container.h"
|
||||
#include "ui/await.h"
|
||||
#include "ui/box.h"
|
||||
#include "ui/button.h"
|
||||
#include "ui/combobox.h"
|
||||
@ -125,6 +126,7 @@ struct Dialog {
|
||||
// Reference used to keep the dialog alive (so it's not garbage
|
||||
// collected) when it's visible.
|
||||
int showRef = LUA_REFNIL;
|
||||
int dlgStateRef = LUA_REFNIL;
|
||||
lua_State* L = nullptr;
|
||||
|
||||
Dialog(const ui::Window::Type windowType, const std::string& title)
|
||||
@ -152,6 +154,11 @@ struct Dialog {
|
||||
this->L = L;
|
||||
lua_pushvalue(L, 1);
|
||||
showRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
// Keep a reference of this thread state while the dialog is still alive
|
||||
// because it will be reused when calling the event handlers to create
|
||||
// new lua threads.
|
||||
lua_pushthread(L);
|
||||
dlgStateRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,8 +170,14 @@ struct Dialog {
|
||||
void unrefShow()
|
||||
{
|
||||
if (showRef != LUA_REFNIL) {
|
||||
int status = lua_status(this->L);
|
||||
TRACE("status: %d %d\n", showRef, status);
|
||||
|
||||
// this->L
|
||||
luaL_unref(this->L, LUA_REGISTRYINDEX, showRef);
|
||||
luaL_unref(this->L, LUA_REGISTRYINDEX, dlgStateRef);
|
||||
showRef = LUA_REFNIL;
|
||||
dlgStateRef = LUA_REFNIL;
|
||||
L = nullptr;
|
||||
}
|
||||
}
|
||||
@ -336,18 +349,7 @@ void Dialog_connect_signal(lua_State* L,
|
||||
callback(L, std::forward<Args>(args)...);
|
||||
|
||||
if (lua_isfunction(L, -2)) {
|
||||
try {
|
||||
if (lua_pcall(L, 1, 0, 0)) {
|
||||
if (const char* s = lua_tostring(L, -1))
|
||||
App::instance()->scriptEngine()->consolePrint(s);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
// This is used to catch unhandled exception or for
|
||||
// example, std::runtime_error exceptions when a Tx() is
|
||||
// created without an active sprite.
|
||||
App::instance()->scriptEngine()->handleException(ex);
|
||||
}
|
||||
App::instance()->scriptEngine()->callInTask(L, 1);
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1); // Pop the value which should have been a function
|
||||
@ -358,63 +360,65 @@ void Dialog_connect_signal(lua_State* L,
|
||||
|
||||
int Dialog_new(lua_State* L)
|
||||
{
|
||||
// If we don't have UI, just return nil
|
||||
if (!App::instance()->isGui())
|
||||
return 0;
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
// 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());
|
||||
// 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);
|
||||
}
|
||||
lua_pop(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, "onclose");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
Dialog_connect_signal(L, -2, dlg->window.Close, [](lua_State*, CloseEvent&) {
|
||||
// Do nothing
|
||||
});
|
||||
type = lua_getfield(L, 1, "notitlebar");
|
||||
if (type != LUA_TNIL && lua_toboolean(L, -1))
|
||||
windowType = ui::Window::WithoutTitleBar;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
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();
|
||||
auto dlg = push_new<Dialog>(L, windowType, title);
|
||||
|
||||
TRACE_DIALOG("Dialog_new", dlg);
|
||||
return 1;
|
||||
// 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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
int Dialog_gc(lua_State* L)
|
||||
@ -1879,31 +1883,34 @@ int Dialog_set_bounds(lua_State* L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define wrap(func) \
|
||||
[](lua_State* L) -> int { return ui::await<int>([L]() -> int { return func(L); }); }
|
||||
|
||||
const luaL_Reg Dialog_methods[] = {
|
||||
{ "__gc", Dialog_gc },
|
||||
{ "show", Dialog_show },
|
||||
{ "showMenu", Dialog_showMenu },
|
||||
{ "close", Dialog_close },
|
||||
{ "newrow", Dialog_newrow },
|
||||
{ "separator", Dialog_separator },
|
||||
{ "label", Dialog_label },
|
||||
{ "button", Dialog_button },
|
||||
{ "check", Dialog_check },
|
||||
{ "radio", Dialog_radio },
|
||||
{ "menuItem", Dialog_menuItem },
|
||||
{ "entry", Dialog_entry },
|
||||
{ "number", Dialog_number },
|
||||
{ "slider", Dialog_slider },
|
||||
{ "combobox", Dialog_combobox },
|
||||
{ "color", Dialog_color },
|
||||
{ "shades", Dialog_shades },
|
||||
{ "file", Dialog_file },
|
||||
{ "canvas", Dialog_canvas },
|
||||
{ "tab", Dialog_tab },
|
||||
{ "endtabs", Dialog_endtabs },
|
||||
{ "modify", Dialog_modify },
|
||||
{ "repaint", Dialog_repaint },
|
||||
{ nullptr, nullptr }
|
||||
{ "__gc", wrap(Dialog_gc) },
|
||||
{ "show", wrap(Dialog_show) },
|
||||
{ "showMenu", Dialog_showMenu },
|
||||
{ "close", Dialog_close },
|
||||
{ "newrow", Dialog_newrow },
|
||||
{ "separator", Dialog_separator },
|
||||
{ "label", Dialog_label },
|
||||
{ "button", wrap(Dialog_button) },
|
||||
{ "check", Dialog_check },
|
||||
{ "radio", Dialog_radio },
|
||||
{ "menuItem", Dialog_menuItem },
|
||||
{ "entry", Dialog_entry },
|
||||
{ "number", Dialog_number },
|
||||
{ "slider", Dialog_slider },
|
||||
{ "combobox", Dialog_combobox },
|
||||
{ "color", Dialog_color },
|
||||
{ "shades", Dialog_shades },
|
||||
{ "file", Dialog_file },
|
||||
{ "canvas", Dialog_canvas },
|
||||
{ "tab", Dialog_tab },
|
||||
{ "endtabs", Dialog_endtabs },
|
||||
{ "modify", Dialog_modify },
|
||||
{ "repaint", Dialog_repaint },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
const Property Dialog_properties[] = {
|
||||
|
@ -35,6 +35,8 @@
|
||||
#include "fmt/format.h"
|
||||
#include "ui/base.h"
|
||||
#include "ui/cursor_type.h"
|
||||
#include "ui/manager.h"
|
||||
#include "ui/message_loop.h"
|
||||
#include "ui/mouse_button.h"
|
||||
|
||||
#include <fstream>
|
||||
@ -213,7 +215,80 @@ void register_websocket_class(lua_State* L);
|
||||
|
||||
void set_app_params(lua_State* L, const Params& params);
|
||||
|
||||
Engine::Engine() : L(luaL_newstate()), m_delegate(nullptr), m_printLastResult(false)
|
||||
#define RUNNING_TASKS "RunningTasks"
|
||||
|
||||
RunScriptTask::RunScriptTask(lua_State* L, int nelems, Func&& func)
|
||||
{
|
||||
m_mainL = L;
|
||||
m_L = lua_newthread(m_mainL);
|
||||
// Save a reference to the lua thread in the registry to avoid garbage collection
|
||||
// before it gets executed.
|
||||
m_LRef = luaL_ref(m_mainL, LUA_REGISTRYINDEX);
|
||||
|
||||
// Move nelems stack elements from main thread to the child lua thread.
|
||||
lua_xmove(m_mainL, m_L, nelems);
|
||||
|
||||
// We use a global table to have access to the state of all the currently
|
||||
// running scripts
|
||||
int type = lua_getglobal(m_L, RUNNING_TASKS);
|
||||
if (type == LUA_TNIL) {
|
||||
lua_newtable(m_L);
|
||||
lua_pushvalue(m_L, -1);
|
||||
lua_setglobal(m_L, RUNNING_TASKS);
|
||||
}
|
||||
|
||||
lua_pushlightuserdata(m_L, m_L);
|
||||
lua_pushlightuserdata(m_L, this);
|
||||
lua_settable(m_L, -3);
|
||||
lua_pop(m_L, 1);
|
||||
|
||||
lua_sethook(
|
||||
m_L,
|
||||
[](lua_State* L, lua_Debug* ar) {
|
||||
if (ar->event == LUA_HOOKCOUNT) {
|
||||
int type = lua_getglobal(L, RUNNING_TASKS);
|
||||
if (type == LUA_TTABLE) {
|
||||
lua_pushlightuserdata(L, L);
|
||||
lua_gettable(L, -2);
|
||||
RunScriptTask* task = (RunScriptTask*)lua_topointer(L, -1);
|
||||
lua_pop(L, 2);
|
||||
if (task->wantsToStop()) {
|
||||
lua_pushliteral(L, "Script stopped");
|
||||
lua_error(L);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
LUA_MASKCOUNT,
|
||||
10);
|
||||
|
||||
// TODO: use the token for allowing the script to report progress somehow.
|
||||
m_task.on_execute([this, func](base::task_token& token) { func(m_L); });
|
||||
}
|
||||
|
||||
RunScriptTask::~RunScriptTask()
|
||||
{
|
||||
luaL_unref(m_mainL, LUA_REGISTRYINDEX, m_LRef);
|
||||
|
||||
// Collect script garbage.
|
||||
lua_gc(m_L, LUA_GCCOLLECT);
|
||||
}
|
||||
|
||||
void RunScriptTask::execute(base::thread_pool& pool)
|
||||
{
|
||||
m_task.start(pool);
|
||||
}
|
||||
|
||||
void RunScriptTask::stop()
|
||||
{
|
||||
m_wantsToStop = true;
|
||||
}
|
||||
|
||||
Engine::Engine()
|
||||
: L(luaL_newstate())
|
||||
, m_delegate(nullptr)
|
||||
, m_threadPool(3)
|
||||
, m_printLastResult(false)
|
||||
{
|
||||
#if _DEBUG
|
||||
int top = lua_gettop(L);
|
||||
@ -525,6 +600,12 @@ Engine::~Engine()
|
||||
|
||||
void Engine::destroy()
|
||||
{
|
||||
// Stop all running tasks.
|
||||
for (auto& task : m_tasks) {
|
||||
task->stop();
|
||||
}
|
||||
m_threadPool.wait_all();
|
||||
|
||||
close_all_dialogs();
|
||||
lua_close(L);
|
||||
L = nullptr;
|
||||
@ -594,6 +675,11 @@ bool Engine::evalCode(const std::string& code, const std::string& filename)
|
||||
}
|
||||
|
||||
void Engine::handleException(const std::exception& ex)
|
||||
{
|
||||
handleException(L, ex);
|
||||
}
|
||||
|
||||
void Engine::handleException(lua_State* L, const std::exception& ex)
|
||||
{
|
||||
luaL_where(L, 1);
|
||||
const char* where = lua_tostring(L, -1);
|
||||
@ -631,6 +717,110 @@ bool Engine::evalFile(const std::string& filename, const Params& params)
|
||||
return result;
|
||||
}
|
||||
|
||||
void Engine::callInTask(lua_State* parentL, int nargs)
|
||||
{
|
||||
executeTask(parentL, nargs + 1, [this, nargs](lua_State* L) {
|
||||
try {
|
||||
if (lua_pcall(L, nargs, 0, 0)) {
|
||||
if (const char* s = lua_tostring(L, -1))
|
||||
consolePrint(s);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
// This is used to catch unhandled exception or for
|
||||
// example, std::runtime_error exceptions when a Tx() is
|
||||
// created without an active sprite.
|
||||
handleException(L, ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Engine::executeTask(lua_State* parentL, int nelems, RunScriptTask::Func&& func)
|
||||
{
|
||||
auto task = std::make_unique<RunScriptTask>(parentL, nelems, std::move(func));
|
||||
auto* taskPtr = task.get();
|
||||
task->onDone([this, taskPtr](base::task_token&) { onTaskDone(taskPtr); });
|
||||
m_tasks.push_back(std::move(task));
|
||||
taskPtr->execute(m_threadPool);
|
||||
}
|
||||
|
||||
void Engine::onTaskDone(const RunScriptTask* task)
|
||||
{
|
||||
for (auto it = m_tasks.begin(); it != m_tasks.end(); ++it) {
|
||||
if ((*it).get() == task) {
|
||||
m_tasks.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Engine::evalUserFileInTask(const std::string& filename, const Params& params)
|
||||
{
|
||||
executeTask(L, 0, [filename, params, this](lua_State* L) {
|
||||
std::string absFilename = base::get_absolute_path(filename);
|
||||
// Set the _SCRIPT_PATH global so require() can find .lua files from
|
||||
// the script path.
|
||||
std::string path = base::get_file_path(absFilename);
|
||||
SetScriptForRequire setScript(L, path.c_str());
|
||||
|
||||
bool ok = true;
|
||||
std::stringstream buf;
|
||||
{
|
||||
std::ifstream s(FSTREAM_PATH(filename));
|
||||
// Returns false if we cannot open the file
|
||||
if (!s) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
buf << s.rdbuf();
|
||||
}
|
||||
|
||||
AddScriptFilename addScript(absFilename);
|
||||
set_app_params(L, params);
|
||||
|
||||
const std::string& code = buf.str();
|
||||
const std::string& atFilename = "@" + absFilename;
|
||||
|
||||
int returnCode;
|
||||
try {
|
||||
if (luaL_loadbuffer(L, code.c_str(), code.size(), atFilename.c_str()) ||
|
||||
lua_pcall(L, 0, 1, 0)) {
|
||||
const char* s = lua_tostring(L, -1);
|
||||
if (s)
|
||||
TRACE("Error: %s\n", s);
|
||||
// onConsoleError(s);
|
||||
ok = false;
|
||||
returnCode = -1;
|
||||
}
|
||||
else {
|
||||
// Return code
|
||||
if (lua_isinteger(L, -1))
|
||||
returnCode = lua_tointeger(L, -1);
|
||||
else
|
||||
returnCode = 0;
|
||||
|
||||
// Code was executed correctly
|
||||
if (m_printLastResult) {
|
||||
if (!lua_isnone(L, -1)) {
|
||||
const char* result = lua_tostring(L, -1);
|
||||
if (result)
|
||||
TRACE("Result: %s\n", result);
|
||||
// onConsolePrint(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
// handleException(ex);
|
||||
TRACE("Exception: %s\n", ex.what());
|
||||
ok = false;
|
||||
returnCode = -1;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Engine::evalUserFile(const std::string& filename, const Params& params)
|
||||
{
|
||||
// Set the _SCRIPT_PATH global so require() can find .lua files from
|
||||
@ -660,6 +850,12 @@ void Engine::stopDebugger()
|
||||
lua_sethook(L, nullptr, 0, 0);
|
||||
}
|
||||
|
||||
void Engine::stopScript()
|
||||
{
|
||||
lua_pushliteral(L, "Script stopped");
|
||||
lua_error(L);
|
||||
}
|
||||
|
||||
void Engine::onConsoleError(const char* text)
|
||||
{
|
||||
if (text && m_delegate)
|
||||
|
@ -16,6 +16,8 @@
|
||||
#include "app/color.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/extensions.h"
|
||||
#include "base/task.h"
|
||||
#include "base/thread_pool.h"
|
||||
#include "base/uuid.h"
|
||||
#include "doc/brush.h"
|
||||
#include "doc/frame.h"
|
||||
@ -84,8 +86,32 @@ public:
|
||||
virtual void endFile(const std::string& file) = 0;
|
||||
};
|
||||
|
||||
class RunScriptTask {
|
||||
public:
|
||||
typedef std::function<void(lua_State* L)> Func;
|
||||
|
||||
RunScriptTask(lua_State* L, int nelems, Func&& func);
|
||||
~RunScriptTask();
|
||||
|
||||
void onDone(base::task::func_t&& funcDone) { m_task.on_done(std::move(funcDone)); }
|
||||
void execute(base::thread_pool& pool);
|
||||
void stop();
|
||||
bool wantsToStop() const { return m_wantsToStop; }
|
||||
|
||||
private:
|
||||
// Lua's main thread state.
|
||||
lua_State* m_mainL;
|
||||
// Lua's thread state based on m_mainL.
|
||||
lua_State* m_L;
|
||||
int m_LRef = LUA_REFNIL;
|
||||
base::task m_task;
|
||||
bool m_wantsToStop = false;
|
||||
};
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
typedef std::vector<std::unique_ptr<RunScriptTask>> Tasks;
|
||||
|
||||
Engine();
|
||||
~Engine();
|
||||
|
||||
@ -101,8 +127,13 @@ public:
|
||||
bool evalCode(const std::string& code, const std::string& filename = std::string());
|
||||
bool evalFile(const std::string& filename, const Params& params = Params());
|
||||
bool evalUserFile(const std::string& filename, const Params& params = Params());
|
||||
bool evalUserFileInTask(const std::string& filename, const Params& params = Params());
|
||||
// Calls the function in the stack with the number of arguments specified by nargs in
|
||||
// a new RunScriptTask.
|
||||
void callInTask(lua_State* L, int nargs);
|
||||
|
||||
void handleException(const std::exception& ex);
|
||||
void handleException(lua_State* L, const std::exception& ex);
|
||||
|
||||
void consolePrint(const char* text) { onConsolePrint(text); }
|
||||
|
||||
@ -112,13 +143,22 @@ public:
|
||||
|
||||
void startDebugger(DebuggerDelegate* debuggerDelegate);
|
||||
void stopDebugger();
|
||||
// Stops the currently running lua chunk
|
||||
void stopScript();
|
||||
|
||||
private:
|
||||
void onConsoleError(const char* text);
|
||||
void onConsolePrint(const char* text);
|
||||
// Creates a new RunScriptTask based on parentL and moving nelems from parentL's stack
|
||||
// to the child lua thread stack
|
||||
void executeTask(lua_State* parentL, int nelems, RunScriptTask::Func&& func);
|
||||
void onTaskDone(const RunScriptTask* task);
|
||||
static void checkProgress(lua_State* L, lua_Debug* ar);
|
||||
|
||||
lua_State* L;
|
||||
EngineDelegate* m_delegate;
|
||||
base::thread_pool m_threadPool;
|
||||
Tasks m_tasks;
|
||||
bool m_printLastResult;
|
||||
int m_returnCode;
|
||||
};
|
||||
|
57
src/ui/await.h
Normal file
57
src/ui/await.h
Normal file
@ -0,0 +1,57 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef UI_AWAIT_H_INCLUDED
|
||||
#define UI_AWAIT_H_INCLUDED
|
||||
#include <type_traits>
|
||||
#pragma once
|
||||
|
||||
#include "os/event.h"
|
||||
#include "os/event_queue.h"
|
||||
#include "ui/system.h"
|
||||
|
||||
#include <functional>
|
||||
#include <future>
|
||||
|
||||
namespace ui {
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (ui::is_ui_thread())
|
||||
return func();
|
||||
|
||||
std::promise<T> promise;
|
||||
std::future<T> future = promise.get_future();
|
||||
|
||||
// Queue the event
|
||||
os::Event ev;
|
||||
ev.setType(os::Event::Callback);
|
||||
ev.setCallback([func, &promise]() {
|
||||
try {
|
||||
if constexpr (std::is_void<T>())
|
||||
func();
|
||||
else
|
||||
promise.set_value(func());
|
||||
}
|
||||
catch (...) {
|
||||
promise.set_exception(std::current_exception());
|
||||
}
|
||||
});
|
||||
os::queue_event(ev);
|
||||
// Wait for the future
|
||||
return future.get();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user