diff options
author | Evan Welsh <contact@evanwelsh.com> | 2021-09-04 21:04:05 -0700 |
---|---|---|
committer | Evan Welsh <contact@evanwelsh.com> | 2021-09-04 21:15:10 -0700 |
commit | e51e0308d0bc8e2b8be23f177ee52bfe01ae83a4 (patch) | |
tree | 322eccfe7ea7695e1137a0fedb87b8f0a07a6d8c | |
parent | 71c481cc483e498365f4baca57f5581ecb8a3c43 (diff) | |
download | gjs-ewlsh/top-level-await-mainloop.tar.gz |
Add implicit main loop for dynamic importsewlsh/top-level-await-mainloop
-rw-r--r-- | gjs/context-private.h | 4 | ||||
-rw-r--r-- | gjs/context.cpp | 10 | ||||
-rw-r--r-- | gjs/internal.cpp | 9 | ||||
-rw-r--r-- | gjs/mainloop.cpp | 55 | ||||
-rw-r--r-- | gjs/mainloop.h | 30 | ||||
-rw-r--r-- | meson.build | 1 |
6 files changed, 108 insertions, 1 deletions
diff --git a/gjs/context-private.h b/gjs/context-private.h index 8004dbc0..6244a107 100644 --- a/gjs/context-private.h +++ b/gjs/context-private.h @@ -37,6 +37,7 @@ #include "gjs/context.h" #include "gjs/jsapi-util.h" #include "gjs/macros.h" +#include "gjs/mainloop.h" #include "gjs/profiler.h" namespace js { @@ -81,6 +82,7 @@ class GjsContextPrivate : public JS::JobQueue { JobQueueStorage m_job_queue; GSource* m_promise_queue_source; GCancellable* m_promise_queue_source_cancellable; + Gjs::MainLoop* m_main_loop; std::vector<std::pair<DestroyNotify, void*>> m_destroy_notifications; std::vector<Gjs::Closure::Ptr> m_async_closures; @@ -176,6 +178,8 @@ class GjsContextPrivate : public JS::JobQueue { [[nodiscard]] JSObject* internal_global() const { return m_internal_global.get(); } + void main_loop_ref() { m_main_loop->ref(); } + bool main_loop_unref() { return m_main_loop->unref(); } [[nodiscard]] GjsProfiler* profiler() const { return m_profiler; } [[nodiscard]] const GjsAtoms& atoms() const { return *m_atoms; } [[nodiscard]] bool destroying() const { return m_destroying.load(); } diff --git a/gjs/context.cpp b/gjs/context.cpp index 83d5bedc..06f43e31 100644 --- a/gjs/context.cpp +++ b/gjs/context.cpp @@ -75,6 +75,7 @@ #include "gjs/importer.h" #include "gjs/internal.h" #include "gjs/jsapi-util.h" +#include "gjs/mainloop.h" #include "gjs/mem.h" #include "gjs/module.h" #include "gjs/native.h" @@ -453,6 +454,7 @@ void GjsContextPrivate::dispose(void) { delete m_fundamental_table; delete m_gtype_table; delete m_atoms; + delete m_main_loop; /* Tear down JS */ JS_DestroyContext(m_cx); @@ -564,6 +566,8 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context) m_atoms = new GjsAtoms(); + m_main_loop = new Gjs::MainLoop(); + if (ObjectBox::gtype() == 0) g_error("Failed to initialize JSObject GType"); @@ -1264,6 +1268,9 @@ bool GjsContextPrivate::eval(const char* script, ssize_t script_len, JS::RootedValue retval(m_cx); bool ok = eval_with_scope(nullptr, script, script_len, filename, &retval); + if (ok) + m_main_loop->spin(m_public_context); + /* The promise job queue should be drained even on error, to finish * outstanding async tasks before the context is torn down. Drain after * uncaught exceptions have been reported since draining runs callbacks. */ @@ -1328,6 +1335,9 @@ bool GjsContextPrivate::eval_module(const char* identifier, bool ok = JS::ModuleEvaluate(m_cx, obj); + if (ok) + m_main_loop->spin(m_public_context); + /* The promise job queue should be drained even on error, to finish * outstanding async tasks before the context is torn down. Drain after * uncaught exceptions have been reported since draining runs callbacks. diff --git a/gjs/internal.cpp b/gjs/internal.cpp index ed856ac4..be28ee38 100644 --- a/gjs/internal.cpp +++ b/gjs/internal.cpp @@ -39,6 +39,7 @@ #include "gjs/importer.h" #include "gjs/jsapi-util-args.h" #include "gjs/jsapi-util.h" +#include "gjs/mainloop.h" #include "gjs/mem-private.h" #include "gjs/module.h" #include "gjs/native.h" @@ -503,7 +504,10 @@ class PromiseData { static void load_async_callback(GObject* file, GAsyncResult* res, void* data) { std::unique_ptr<PromiseData> promise(PromiseData::from_ptr(data)); - JSAutoRealm ac(promise->cx, gjs_get_import_global(promise->cx)); + GjsContextPrivate* priv = GjsContextPrivate::from_cx(promise->cx); + priv->main_loop_unref(); + + JSAutoRealm ac(promise->cx, priv->global()); char* contents; size_t length; @@ -551,6 +555,9 @@ static bool load_async_executor(JSContext* cx, unsigned argc, JS::Value* vp) { auto* data = new PromiseData(cx, JS_GetObjectFunction(resolve), JS_GetObjectFunction(reject)); + + // Hold a reference to the main loop until this function resolves... + GjsContextPrivate::from_cx(cx)->main_loop_ref(); g_file_load_contents_async(file, nullptr, load_async_callback, data); args.rval().setUndefined(); diff --git a/gjs/mainloop.cpp b/gjs/mainloop.cpp new file mode 100644 index 00000000..efe9d05a --- /dev/null +++ b/gjs/mainloop.cpp @@ -0,0 +1,55 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later +// SPDX-FileCopyrightText: 2021 Evan Welsh <contact@evanwelsh.com> + +#include <gio/gio.h> +#include <glib.h> + +#include "gjs/context-private.h" +#include "gjs/mainloop.h" + +namespace Gjs { + +MainLoop::MainLoop() { m_refcount = 0; } + +void MainLoop::ref() { m_refcount++; } + +bool MainLoop::unref() { + if (m_refcount == 0) + return false; + + m_refcount--; + return true; +} + +void MainLoop::spin(GjsContext* context) { + auto priv = GjsContextPrivate::from_object(context); + + // Check if System.exit() has been called. + if (priv->should_exit(nullptr)) + return; + + GjsAutoPointer<GMainContext, GMainContext, g_main_context_unref> + main_context(g_main_context_ref_thread_default()); + + do { + if (priv->should_exit(nullptr)) + break; + + // Allow the loop to block if the event loop is referenced. + bool can_block = m_refcount > 0; + // Only run the loop if there are pending jobs. + if (g_main_context_pending(main_context)) + g_main_context_iteration(main_context, can_block); + + // Check if System.exit() has been called. + if (priv->should_exit(nullptr)) + break; + } while ( + // If there are pending sources or the job queue is not empty + (m_refcount > 0 || !priv->empty()) && + // and System.exit() has not been called + // continue spinning the event loop. + !priv->should_exit(nullptr)); +} +}; // namespace Gjs diff --git a/gjs/mainloop.h b/gjs/mainloop.h new file mode 100644 index 00000000..9ab78a70 --- /dev/null +++ b/gjs/mainloop.h @@ -0,0 +1,30 @@ +/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +/* + * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later + * SPDX-FileCopyrightText: 2021 Evan Welsh <contact@evanwelsh.com> + */ + +#ifndef GJS_MAINLOOP_H_ +#define GJS_MAINLOOP_H_ + +#include <config.h> +#include <js/TypeDecls.h> + +#include "gjs/context.h" + +namespace Gjs { +class MainLoop { + uint32_t m_refcount; + + public: + MainLoop(); + + void ref(); + + bool unref(); + + void spin(GjsContext* context); +}; +}; // namespace Gjs + +#endif // GJS_MAINLOOP_H_ diff --git a/meson.build b/meson.build index bbfee34b..05c896e9 100644 --- a/meson.build +++ b/meson.build @@ -401,6 +401,7 @@ libgjs_sources = [ 'gjs/global.cpp', 'gjs/global.h', 'gjs/importer.cpp', 'gjs/importer.h', 'gjs/internal.cpp', 'gjs/internal.h', + 'gjs/mainloop.cpp', 'gjs/mainloop.h', 'gjs/mem.cpp', 'gjs/mem-private.h', 'gjs/module.cpp', 'gjs/module.h', 'gjs/native.cpp', 'gjs/native.h', |