summaryrefslogtreecommitdiff
path: root/gi/closure.h
blob: bf074c67d6719d89889516bb72808caa97d50955 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
// SPDX-FileCopyrightText: 2008 litl, LLC
// SPDX-FileCopyrightText: 2021 Canonical Ltd.
// SPDX-FileContributor: Marco Trevisan <marco.trevisan@canonical.com>

#ifndef GI_CLOSURE_H_
#define GI_CLOSURE_H_

#include <config.h>

#include <glib-object.h>
#include <stddef.h>

#include <js/TypeDecls.h>

#include "gi/utils-inl.h"
#include "gjs/jsapi-util-root.h"
#include "gjs/jsapi-util.h"
#include "gjs/macros.h"

class JSTracer;
namespace JS {
class HandleValueArray;
}

namespace Gjs {

class Closure : public GClosure {
 protected:
    Closure(JSContext*, JSObject* callable, bool root, const char* description);
    ~Closure() { unset_context(); }

    // Need to call this if inheriting from Closure to call the dtor
    template <class C>
    constexpr void add_finalize_notifier() {
        static_assert(std::is_base_of_v<Closure, C>);
        g_closure_add_finalize_notifier(
            this, nullptr,
            [](void*, GClosure* closure) { static_cast<C*>(closure)->~C(); });
    }

    void* operator new(size_t size) {
        return g_closure_new_simple(size, nullptr);
    }

    void operator delete(void* p) { unref(static_cast<Closure*>(p)); }

    static Closure* ref(Closure* self) {
        return static_cast<Closure*>(g_closure_ref(self));
    }
    static void unref(Closure* self) { g_closure_unref(self); }

 public:
    using Ptr = GjsAutoPointer<Closure, Closure, unref, ref>;

    [[nodiscard]] constexpr static Closure* for_gclosure(GClosure* gclosure) {
        // We need to do this in order to ensure this is a constant expression
        return static_cast<Closure*>(static_cast<void*>(gclosure));
    }

    [[nodiscard]] static Closure* create(JSContext* cx, JSObject* callable,
                                         const char* description, bool root) {
        auto* self = new Closure(cx, callable, root, description);
        self->add_finalize_notifier<Closure>();
        return self;
    }

    [[nodiscard]] static Closure* create_marshaled(JSContext* cx,
                                                   JSObject* callable,
                                                   const char* description) {
        auto* self = new Closure(cx, callable, true /* root */, description);
        self->add_finalize_notifier<Closure>();
        g_closure_set_marshal(self, marshal_cb);
        return self;
    }

    [[nodiscard]] static Closure* create_for_signal(JSContext* cx,
                                                    JSObject* callable,
                                                    const char* description,
                                                    int signal_id) {
        auto* self = new Closure(cx, callable, false /* root */, description);
        self->add_finalize_notifier<Closure>();
        g_closure_set_meta_marshal(self, gjs_int_to_pointer(signal_id),
                                   marshal_cb);
        return self;
    }

    constexpr JSObject* callable() const { return m_callable; }
    [[nodiscard]] constexpr JSContext* context() const { return m_cx; }
    [[nodiscard]] constexpr bool is_valid() const { return !!m_cx; }
    GJS_JSAPI_RETURN_CONVENTION bool invoke(JS::HandleObject,
                                            const JS::HandleValueArray&,
                                            JS::MutableHandleValue);

    void trace(JSTracer* tracer) {
        if (m_callable)
            m_callable.trace(tracer, "signal connection");
    }

 private:
    void unset_context();

    void reset() {
        unset_context();
        m_callable.reset();
        m_cx = nullptr;
    }

    static void marshal_cb(GClosure* closure, GValue* ret, unsigned n_params,
                           const GValue* params, void* hint, void* data) {
        for_gclosure(closure)->marshal(ret, n_params, params, hint, data);
    }

    static void global_context_notifier_cb(JSContext*, void* data) {
        static_cast<Closure*>(data)->global_context_finalized();
    }

    void closure_invalidated();
    void closure_set_invalid();
    void global_context_finalized();
    void marshal(GValue* ret, unsigned n_parms, const GValue* params,
                 void* hint, void* data);

    //  The saved context is used for lifetime management, so that the closure
    //  will be torn down with the context that created it.
    //  The context could be attached to the default context of the runtime
    //  using if we wanted the closure to survive the context that created it.
    JSContext* m_cx;
    GjsMaybeOwned<JSObject*> m_callable;
};

}  // namespace Gjs

#endif  // GI_CLOSURE_H_