diff options
| author | Akshay Nair <phenax5@gmail.com> | 2025-03-21 19:00:45 +0530 |
|---|---|---|
| committer | Akshay Nair <phenax5@gmail.com> | 2025-03-21 19:44:57 +0530 |
| commit | 7ec8336431787156826185628ad3ee05dc327d2a (patch) | |
| tree | 0b984450aa1ee09f6592eadb898b17ffddd45219 | |
| parent | fdeb33d34a97d062a120f72da919f81d7b1d45bf (diff) | |
| download | null-browser-7ec8336431787156826185628ad3ee05dc327d2a.tar.gz null-browser-7ec8336431787156826185628ad3ee05dc327d2a.zip | |
Add event loop with uv
| -rw-r--r-- | spec/LuaRuntimeSpec.cpp | 76 | ||||
| -rw-r--r-- | spec/main.cpp | 2 | ||||
| -rw-r--r-- | src/AsyncEventLoop.cpp | 94 | ||||
| -rw-r--r-- | src/AsyncEventLoop.hpp | 40 | ||||
| -rw-r--r-- | src/LuaRuntime.cpp | 75 | ||||
| -rw-r--r-- | src/LuaRuntime.hpp | 8 | ||||
| -rw-r--r-- | src/utils.hpp | 2 | ||||
| -rwxr-xr-x | templates/class.sh | 31 | ||||
| -rwxr-xr-x | templates/qwidget.sh (renamed from templates/qclass.sh) | 7 |
9 files changed, 295 insertions, 40 deletions
diff --git a/spec/LuaRuntimeSpec.cpp b/spec/LuaRuntimeSpec.cpp new file mode 100644 index 0000000..149e1e1 --- /dev/null +++ b/spec/LuaRuntimeSpec.cpp @@ -0,0 +1,76 @@ +#include "testUtils.h" +#include <QtCore> +#include <atomic> +#include <uv.h> + +#include "LuaRuntime.hpp" + +class LuaRuntimeSpec : public QObject { + Q_OBJECT + +private slots: + void cleanupTestCase() { uv_library_shutdown(); } + + void testSanityCheckBuiltins() { + auto lua = LuaRuntime::instance(); + + it("evaluates simple expression") { + lua->startEventLoop(); + std::atomic<int> foobar = 2; + + lua->queueTask([&foobar]() { foobar = 10; }); + + lua->evaluate(R"( + print('Hello -- '); + local timer = uv.new_timer(); + timer:start(1000, 0, function() + print('inside timer 1') + print('inside timer 2') + print('inside timer 3') + print('inside timer 4') + print('inside timer 5') + timer:close() + end); + print('-- end'); + )"); + lua->queueTask([]() { qDebug() << "---- 1"; }); + lua->queueTask([]() { qDebug() << "---- 2"; }); + lua->queueTask([]() { qDebug() << "---- 3"; }); + std::this_thread::sleep_for(std::chrono::seconds(2)); + // std::this_thread::sleep_for(std::chrono::milliseconds(20)); + lua->stopEventLoop(); + + qDebug() << "foobar" << foobar; + + QCOMPARE(1, 1); + } + } + + void testSanityCheckBuiltins2() { + auto lua = LuaRuntime::instance(); + + it("evaluates again") { + lua->startEventLoop(); + + lua->queueTask([]() { qDebug() << "---- 5"; }); + lua->evaluate(R"( + print('Hello -- '); + local timer = uv.new_timer(); + timer:start(1000, 0, function() + print('%%%%% blagb') + timer:close() + end); + print('-- end'); + )"); + lua->queueTask([]() { qDebug() << "---- 5"; }); + // TODO: Impl + std::this_thread::sleep_for(std::chrono::seconds(2)); + lua->stopEventLoop(); + + QCOMPARE(1, 1); + } + } +}; + +QTEST_REGISTER(LuaRuntimeSpec) +#include "LuaRuntimeSpec.moc" diff --git a/spec/main.cpp b/spec/main.cpp index f238e70..352ecba 100644 --- a/spec/main.cpp +++ b/spec/main.cpp @@ -4,7 +4,5 @@ int main(int argc, char **argv) { QApplication app(argc, argv); - printf("foobar"); - return runAllTests(); } diff --git a/src/AsyncEventLoop.cpp b/src/AsyncEventLoop.cpp new file mode 100644 index 0000000..360a45e --- /dev/null +++ b/src/AsyncEventLoop.cpp @@ -0,0 +1,94 @@ +#include <QtCore> +#include <functional> +#include <mutex> +#include <thread> +#include <uv.h> + +#include "AsyncEventLoop.hpp" + +AsyncEventLoop::AsyncEventLoop() { + // UV Loop + loop = (uv_loop_t *)malloc(sizeof(uv_loop_t)); + uv_loop_init(loop); + + uv_async_init(loop, &asyncHandle, AsyncEventLoop::asyncHandleCallback); + asyncHandle.data = this; + + loopThread = std::thread(&AsyncEventLoop::runLoop, this); + + // Wait for thread to start + while (!isLoopRunning) + std::this_thread::sleep_for(std::chrono::milliseconds(50)); +} + +void AsyncEventLoop::processTasks() { + std::queue<std::function<void()>> tasks; + + { + std::lock_guard<std::mutex> lock(tasksQueueMutex); + tasksQueue.swap(tasks); + } + + while (!tasks.empty()) { + auto task = std::move(tasks.front()); + tasks.pop(); + task(); + } +} + +void AsyncEventLoop::runLoop() { + isLoopRunning = true; + while (isLoopRunning) { + int result = uv_run(loop, UV_RUN_ONCE); + if (result == 0) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + uv_run(loop, UV_RUN_NOWAIT); +} + +AsyncEventLoop::~AsyncEventLoop() { + if (!isLoopRunning) + return; + isLoopRunning = false; + + // Clear the tasks queue + { + std::lock_guard<std::mutex> lock(tasksQueueMutex); + std::queue<std::function<void()>>().swap(tasksQueue); + } + + // Wake it up. Stab it to death. + uv_async_send(&asyncHandle); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + uv_stop(loop); + + // Close all handles + AsyncEventLoop::closeHandle((uv_handle_t *)&asyncHandle); + uv_walk(loop, AsyncEventLoop::closeHandle, nullptr); + while (uv_run(loop, UV_RUN_ONCE) != 0) + ; + // TODO: Fix pending handler case (setTimeout(100) wait(20) close() -> error) + + qDebug() << "join start"; + if (loopThread.joinable()) + loopThread.join(); + qDebug() << "join done"; + + while (uv_loop_close(loop) == UV_EBUSY) { + uv_walk(loop, AsyncEventLoop::closeHandle, nullptr); + uv_run(loop, UV_RUN_NOWAIT); + } + free(loop); + loop = nullptr; +} + +void AsyncEventLoop::asyncHandleCallback(uv_async_t *handle) { + auto *runtime = static_cast<AsyncEventLoop *>(handle->data); + runtime->processTasks(); +} + +void AsyncEventLoop::closeHandle(uv_handle_t *handle, void *arg) { + if (!uv_is_closing(handle)) { + uv_close(handle, [](uv_handle_t *h) { h->data = nullptr; }); + } +} diff --git a/src/AsyncEventLoop.hpp b/src/AsyncEventLoop.hpp new file mode 100644 index 0000000..ebe2cad --- /dev/null +++ b/src/AsyncEventLoop.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include <atomic> +#include <functional> +#include <mutex> +#include <queue> +#include <thread> +#include <uv.h> + +#include "utils.hpp" + +class AsyncEventLoop { +public: + AsyncEventLoop(); + ~AsyncEventLoop(); + + DEFINE_GETTER(getUVLoop, loop) + + template <typename F> void queueTask(F &&task) { + { + std::lock_guard<std::mutex> lock(tasksQueueMutex); + tasksQueue.push(std::forward<F>(task)); + } + uv_async_send(&asyncHandle); + } + +protected: + void runLoop(); + void processTasks(); + static void asyncHandleCallback(uv_async_t *handle); + static void closeHandle(uv_handle_t *handle, void *arg = nullptr); + +private: + uv_loop_t *loop; + std::thread loopThread; + uv_async_t asyncHandle; + std::atomic<bool> isLoopRunning = false; + std::queue<std::function<void()>> tasksQueue; + std::mutex tasksQueueMutex; +}; diff --git a/src/LuaRuntime.cpp b/src/LuaRuntime.cpp index d89f7dc..e61fbf5 100644 --- a/src/LuaRuntime.cpp +++ b/src/LuaRuntime.cpp @@ -4,16 +4,16 @@ extern "C" { #include <luv.h> } +#include "AsyncEventLoop.hpp" #include "LuaRuntime.hpp" +const char *uv_global_name = "uv"; +const char *web_global_name = "web"; + LuaRuntime::LuaRuntime() { state = luaL_newstate(); luaL_openlibs(state); - // Load `uv` (luv) - luaopen_luv(state); - lua_setglobal(state, "uv"); - // Load `web` luaL_Reg weblib[] = { {"open", &LuaRuntime::lua_onUrlOpen}, @@ -21,39 +21,42 @@ LuaRuntime::LuaRuntime() { {NULL, NULL}, }; luaL_newlib(state, weblib); - lua_setglobal(state, "web"); + lua_setglobal(state, web_global_name); +} + +void LuaRuntime::startEventLoop() { + if (eventLoop != nullptr) + stopEventLoop(); + + // Init event loop + eventLoop = new AsyncEventLoop(); - // auto pp = R"( - // print('Hello -- '); - // local h = uv.fs_open('foobar', 'w', tonumber('644', 8), function(err, h) - // assert(not err, err); - // print('inside'); - // print(h); - // print(err); - // uv.fs_write(h, 'Hello world'); - // end); - // print(h); - // uv.run(); - // print('-- end'); - // )"; - // auto pp = R"( - // print('Hello -- '); - // local t = uv.new_timer(); - // uv.timer_start(t, 4000, 0, function() - // print('after time') - // end); - // uv.run(); - // print('-- end'); - // )"; - // if (luaL_dostring(state, pp)) { - // qDebug() << "Lua Error: " << lua_tostring(state, -1); - // } else { - // qDebug() << "succ"; - // } + // Load `uv` (luv) + luv_set_loop(state, eventLoop->getUVLoop()); + luaopen_luv(state); + lua_setglobal(state, uv_global_name); +} + +void LuaRuntime::stopEventLoop() { + if (eventLoop == nullptr) + return; + delete eventLoop; + eventLoop = nullptr; + + // Clear the uv global + lua_pushnil(state); + lua_setglobal(state, uv_global_name); + lua_gc(state, LUA_GCCOLLECT, 0); } void LuaRuntime::evaluate(QString code) { - luaL_dostring(state, code.toStdString().c_str()); + eventLoop->queueTask([this, code]() { + if (luaL_dostring(state, code.toStdString().c_str())) { + qDebug() << "Lua Error: " << lua_tostring(state, -1); + lua_pop(state, 1); + } else + qDebug() << "done"; + }); } int LuaRuntime::lua_onUrlOpen(lua_State *state) { @@ -70,4 +73,8 @@ int LuaRuntime::lua_onUrlTabOpen(lua_State *state) { return 1; } -LuaRuntime::~LuaRuntime() { lua_close(state); } +LuaRuntime::~LuaRuntime() { + stopEventLoop(); + lua_close(state); + state = nullptr; +} diff --git a/src/LuaRuntime.hpp b/src/LuaRuntime.hpp index 5bfc82c..443dec5 100644 --- a/src/LuaRuntime.hpp +++ b/src/LuaRuntime.hpp @@ -1,7 +1,9 @@ #pragma once + #include <QtCore> #include <lua.hpp> +#include "AsyncEventLoop.hpp" #include "widgets/WebViewStack.hpp" class LuaRuntime : public QObject { @@ -15,6 +17,11 @@ public: void evaluate(QString code); + void stopEventLoop(); + void startEventLoop(); + + DELEGATE(eventLoop, queueTask, queueTask) + signals: void urlOpened(QString url, OpenType openType); @@ -26,4 +33,5 @@ protected: private: lua_State *state; + AsyncEventLoop *eventLoop = nullptr; }; diff --git a/src/utils.hpp b/src/utils.hpp index 0650ac2..fbe5597 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -8,4 +8,4 @@ template <typename Arg> void METHOD(Arg val) { PROPERTY = val; } #define DEFINE_GETTER(METHOD, EXPR) \ - template <typename Arg> decltype(auto) METHOD() { return EXPR; } + decltype(auto) METHOD() { return EXPR; } diff --git a/templates/class.sh b/templates/class.sh new file mode 100755 index 0000000..ad60acf --- /dev/null +++ b/templates/class.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env sh + +set -e -o pipefail + +class_name="$1" +path="$2" + +[ -z "$class_name" ] && exit 1 + +if ! [ -z "$path" ]; then + mkdir -p "./src/$path" +fi + +# Header +echo "#pragma once + +class $class_name { + Q_OBJECT + + public: + $class_name(); +}; +" > "./src/$path/$class_name.hpp" + +# Implementation +echo " + +#include \"$path/$class_name.hpp\" + +$class_name::$class_name() {} +" > "./src/$path/$class_name.cpp" diff --git a/templates/qclass.sh b/templates/qwidget.sh index 8ad085a..6706e19 100755 --- a/templates/qclass.sh +++ b/templates/qwidget.sh @@ -7,8 +7,9 @@ path="$2" [ -z "$class_name" ] && exit 1 -mkdir -p "./include/$path"; -mkdir -p "./src/$path"; +if ! [ -z "$path" ]; then + mkdir -p "./src/$path" +fi # Header echo "#pragma once @@ -22,7 +23,7 @@ class $class_name : public QWidget { public: $class_name(); }; -" > "./include/$path/$class_name.hpp" +" > "./src/$path/$class_name.hpp" # Implementation echo "#include <QtCore> |
