aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2025-03-21 19:00:45 +0530
committerAkshay Nair <phenax5@gmail.com>2025-03-21 19:44:57 +0530
commit7ec8336431787156826185628ad3ee05dc327d2a (patch)
tree0b984450aa1ee09f6592eadb898b17ffddd45219
parentfdeb33d34a97d062a120f72da919f81d7b1d45bf (diff)
downloadnull-browser-7ec8336431787156826185628ad3ee05dc327d2a.tar.gz
null-browser-7ec8336431787156826185628ad3ee05dc327d2a.zip
Add event loop with uv
-rw-r--r--spec/LuaRuntimeSpec.cpp76
-rw-r--r--spec/main.cpp2
-rw-r--r--src/AsyncEventLoop.cpp94
-rw-r--r--src/AsyncEventLoop.hpp40
-rw-r--r--src/LuaRuntime.cpp75
-rw-r--r--src/LuaRuntime.hpp8
-rw-r--r--src/utils.hpp2
-rwxr-xr-xtemplates/class.sh31
-rwxr-xr-xtemplates/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>