Generated reference › API — ICoreEssentials/UI/Signals
kind: generated#api#icoreessentials-ui-signals

API — ICoreEssentials/UI/Signals

The public contract of 3 header(s) under src/ICoreEssentials/UI/Signals — 7 class/struct definition(s), 28 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.

ICoreMainThread.h#

src/ICoreEssentials/UI/Signals/ICoreMainThread.h

ICoreMainThread#

ICoreMainThread.h:19 · class · 5 declaration(s)

The one door to the GUI thread.

class ICoreMainThread {
public:
    ICoreMainThread() = delete;

    // Run `fn` on the GUI thread, queued behind whatever the event loop is
    // already doing.
    static void post(std::function<void()> fn);

    // Run `fn` NOW when already on the GUI thread, queue it otherwise --
    // Qt::AutoConnection's dispatch, where post() above is always-queued.
    // Pick deliberately: a caller escaping a destructor or paint context
    // needs post()'s returns-first guarantee; a caller that just wants the
    // work done soonest on the right thread wants this.
    static void runNowOrPost(std::function<void()> fn);

    static bool isMainThread();

    // QApplication::quit(), behind the boundary. ONLY the fallback for
    // processes that never construct an icore::Application -- under the SDK
    // entry point quit() acts on a loop that is not running and does nothing,
    // so route through icore::Application::requestQuit() first and call this
    // when there is no live instance.
    static void quitApplicationLoopFallback();
};
};

ICoreSignal.h#

src/ICoreEssentials/UI/Signals/ICoreSignal.h

The project's replacement for Qt signals/slots at every call site OUTSIDE the wrapper layer. Client classes hold public ICoreSignal<...> members where they used to declare signals: sections, and receivers subscribe with connect() where they used to call QObject::connect. No moc, no Q_OBJECT, no QObject base required on either side.

Naming: the sync emit is operator(), the queued emit is post(). There is deliberately no method named emit -- that identifier is a Qt macro in any TU that sees a Qt header, which in this project is most of them.

Threading: connect/disconnect/emit are safe from any thread. Sync emission runs the slots on the calling thread against a snapshot taken under the lock, so a slot may connect or disconnect (even itself) during delivery. post() delivers the snapshot on the GUI thread via ICoreMainThread -- this

SlotBase#

ICoreSignal.h:40 · struct · 1 declaration(s)

struct SlotBase {
public:
    std::atomic<bool> alive { true };
    virtual ~SlotBase() = default;
};
};

Slot#

ICoreSignal.h:46 · struct · bases SlotBase · 1 declaration(s)

struct Slot : SlotBase {
public:
    std::function<void(Args...)> fn;
};
};

ICoreSignalConnection#

ICoreSignal.h:54 · class · pImpl · 6 declaration(s)

Handle to one registration.

class ICoreSignalConnection {
public:
    ICoreSignalConnection();
    ~ICoreSignalConnection();

    // Copyable, and every copy severs the SAME slot -- that is the contract,
    // not an accident, and it survives the Impl because the Impl holds the same
    // weak_ptr. Written out because a unique_ptr<Impl> deletes the implicit
    // copy.
    ICoreSignalConnection(const ICoreSignalConnection& other);
    ICoreSignalConnection& operator=(const ICoreSignalConnection& other);

    void disconnect();

    [[nodiscard]] bool connected() const;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreSignalScope#

ICoreSignal.h:86 · class · pImpl · 6 declaration(s)

Collects connections and severs them all on destruction.

class ICoreSignalScope {
public:
    ICoreSignalScope();
    ICoreSignalScope(const ICoreSignalScope&) = delete;
    ICoreSignalScope& operator=(const ICoreSignalScope&) = delete;

    // Severs every connection it collected.
    ~ICoreSignalScope();

    // const, with the state behind it, so a receiver can subscribe from a
    // const member function -- much of this codebase builds its children from
    // const methods, and what a scope holds is bookkeeping about the owner's
    // subscriptions rather than part of the owner's logical value.
    //
    // ⚠ The `mutable` these two used to need is GONE, and not by oversight:
    // operator-> on a const unique_ptr yields a NON-const Impl&, so state
    // behind an Impl is already writable from a const member. The constness of
    // the interface is unchanged; only the keyword that used to buy it is.
    void add(ICoreSignalConnection connection) const;

    void disconnectAll() const;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreSignal#

ICoreSignal.h:114 · class · 8 declaration(s)

class ICoreSignal {
public:
    ICoreSignal() = default;
    ICoreSignal(const ICoreSignal&) = delete;
    ICoreSignal& operator=(const ICoreSignal&) = delete;

    // Bare connect exists for slots whose lifetime provably exceeds the
    // signal's (a static, or the signal's own owner). Everything else should
    // pass a scope -- an unscoped lambda capturing `this` is exactly the
    // dangling QObject::connect this class exists to retire.
    ICoreSignalConnection connect(std::function<void(Args...)> fn) {
        auto slot = std::make_shared<Slot>();
        slot->fn = std::move(fn);
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            m_slots.push_back(slot);
        }
        return ICoreSignalConnection(slot);
    }

    ICoreSignalConnection connect(const ICoreSignalScope& owner, std::function<void(Args...)> fn) {
        ICoreSignalConnection connection = connect(std::move(fn));
        owner.add(connection);
        return connection;
    }

    // Synchronous emit on the calling thread.
    void operator()(Args... args) {
        for (const auto& slot : snapshot()) {
            if (slot->alive.load()) {
                slot->fn(args...);
            }
        }
    }

    // Queued emit on the GUI thread. Arguments are copied into the hop, so
    // they must be value types (everything crossing this boundary is).
    //
    // NOTE the local is `delivery`, not `slots`: most TUs see Qt's keyword
    // macros, which #define `signals`, `slots` and `emit` away -- none of the
    // three may be used as an identifier anywhere in the wrapper layer.
    void post(Args... args) {
        auto delivery = snapshot();
        auto packed = std::make_tuple(std::move(args)...);
        ICoreMainThread::post([delivery = std::move(delivery), packed = std::move(packed)]() mutable {
            std::apply(
                [&delivery](auto&... unpacked) {
                    for (const auto& slot : delivery) {
                        if (slot->alive.load()) {
                            slot->fn(unpacked...);
                        }
                    }
                },
                packed);
        });
    }

    void disconnectAll() {
        std::lock_guard<std::mutex> lock(m_mutex);
        for (const auto& slot : m_slots) {
            slot->alive.store(false);
        }
        m_slots.clear();
    }

};

ICoreSignalObjectBinding.h#

src/ICoreEssentials/UI/Signals/ICoreSignalObjectBinding.h

Bind an ICoreSignal subscription to the lifetime of a toolkit object.

The normal way to own a subscription is an ICoreSignalScope member on the receiver. This exists for the cases where there is no such member to reach: a template helper handed an arbitrary target*, or a receiver whose type is not ours to edit. It is what the toolkit's own three-argument connect (sender, signal, CONTEXT, slot) was doing -- severing when the context dies.

WRAPPER ZONE ONLY: it names a toolkit object, which is the whole point. Prefer a plain ICoreSignalScope member wherever the receiver has one.

ScopeHolder#

ICoreSignalObjectBinding.h:26 · class · bases public ICoreObject · 1 declaration(s)

Holds one scope and nothing else.

class ScopeHolder : public ICoreObject {
public:
    explicit ScopeHolder(QObject* parent);

    ICoreSignalScope scope;
};
};