Replace boost::signals2 with homegrown CSignal<>

Drops the last boost::signals2 dependency from the GUI/wallet/smessage
notification path. CSignal<> is a std::function-based fan-out signal
with explicit Connection tokens (no equivalent-bind disconnect). Same
semantics for the void-returning case; non-void variant returns the
last-connected slot's result via std::optional.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-26 17:36:05 -07:00
parent 2ba0ecf428
commit 32330b420e
14 changed files with 236 additions and 60 deletions
+2 -2
View File
@@ -6,8 +6,8 @@
#define TRIANGLES_KEYSTORE_H
#include "crypter.h"
#include "signal.h"
#include "sync.h"
#include <boost/signals2/signal.hpp>
class CScript;
@@ -177,7 +177,7 @@ public:
/* Wallet status (encrypted, locked) changed.
* Note: Called without locks held.
*/
boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged;
CSignal<void(CCryptoKeyStore*)> NotifyStatusChanged;
};
#endif
+5 -6
View File
@@ -182,14 +182,13 @@ static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConn
void ClientModel::subscribeToCoreSignals()
{
// Connect signals to client
uiInterface.NotifyBlocksChanged.connect(boost::bind(NotifyBlocksChanged, this));
uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1));
m_core_signal_connections.add(uiInterface.NotifyBlocksChanged.connect(
[this]() { NotifyBlocksChanged(this); }));
m_core_signal_connections.add(uiInterface.NotifyNumConnectionsChanged.connect(
[this](int n) { NotifyNumConnectionsChanged(this, n); }));
}
void ClientModel::unsubscribeFromCoreSignals()
{
// Disconnect signals from client
uiInterface.NotifyBlocksChanged.disconnect(boost::bind(NotifyBlocksChanged, this));
uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1));
m_core_signal_connections.disconnect_all();
}
+4
View File
@@ -3,6 +3,8 @@
#include <QObject>
#include "../signal.h"
class OptionsModel;
class AddressTableModel;
class TransactionTableModel;
@@ -57,6 +59,8 @@ private:
void subscribeToCoreSignals();
void unsubscribeFromCoreSignals();
CSignalConnections m_core_signal_connections;
signals:
void numConnectionsChanged(int count);
void numBlocksChanged(int count, int countOfPeers);
+9 -10
View File
@@ -620,20 +620,19 @@ void MessageModel::subscribeToCoreSignals()
{
qRegisterMetaType<SecMsgStored>("SecMsgStored");
// Connect signals
NotifySecMsgInboxChanged.connect(boost::bind(NotifySecMsgInbox, this, _1));
NotifySecMsgOutboxChanged.connect(boost::bind(NotifySecMsgOutbox, this, _1));
NotifySecMsgWalletUnlocked.connect(boost::bind(NotifySecMsgWallet, this));
m_core_signal_connections.add(NotifySecMsgInboxChanged.connect(
[this](SecMsgStored& hdr) { NotifySecMsgInbox(this, hdr); }));
m_core_signal_connections.add(NotifySecMsgOutboxChanged.connect(
[this](SecMsgStored& hdr) { NotifySecMsgOutbox(this, hdr); }));
m_core_signal_connections.add(NotifySecMsgWalletUnlocked.connect(
[this]() { NotifySecMsgWallet(this); }));
connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
}
void MessageModel::unsubscribeFromCoreSignals()
{
// Disconnect signals
NotifySecMsgInboxChanged.disconnect(boost::bind(NotifySecMsgInbox, this, _1));
NotifySecMsgOutboxChanged.disconnect(boost::bind(NotifySecMsgOutbox, this, _1));
NotifySecMsgWalletUnlocked.disconnect(boost::bind(NotifySecMsgWallet, this));
m_core_signal_connections.disconnect_all();
disconnect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
}
+3
View File
@@ -5,6 +5,7 @@
#include <vector>
#include "allocators.h" /* for SecureString */
#include "../signal.h"
#include "smessage.h"
#include <map>
#include <QSortFilterProxyModel>
@@ -175,6 +176,8 @@ private:
void subscribeToCoreSignals();
void unsubscribeFromCoreSignals();
CSignalConnections m_core_signal_connections;
public slots:
/* Check for new messages */
+11 -8
View File
@@ -545,18 +545,21 @@ static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet,
void WalletModel::subscribeToCoreSignals()
{
// Connect signals to wallet
wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1));
wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5));
wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
m_core_signal_connections.add(wallet->NotifyStatusChanged.connect(
[this](CCryptoKeyStore* w) { NotifyKeyStoreStatusChanged(this, w); }));
m_core_signal_connections.add(wallet->NotifyAddressBookChanged.connect(
[this](CWallet* w, const CTxDestination& address, const std::string& label, bool isMine, ChangeType status) {
NotifyAddressBookChanged(this, w, address, label, isMine, status);
}));
m_core_signal_connections.add(wallet->NotifyTransactionChanged.connect(
[this](CWallet* w, const uint256& hash, ChangeType status) {
NotifyTransactionChanged(this, w, hash, status);
}));
}
void WalletModel::unsubscribeFromCoreSignals()
{
// Disconnect signals from wallet
wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1));
wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5));
wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
m_core_signal_connections.disconnect_all();
}
// WalletModel::UnlockContext implementation
+3
View File
@@ -8,6 +8,7 @@
#include <QMutex>
#include "allocators.h" /* for SecureString */
#include "../signal.h"
class OptionsModel;
class AddressTableModel;
@@ -157,6 +158,8 @@ private:
void unsubscribeFromCoreSignals();
bool checkBalanceChanged();
CSignalConnections m_core_signal_connections;
public slots:
/* Wallet status might have changed */
+155
View File
@@ -0,0 +1,155 @@
// Copyright (c) 2026 The Triangles developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef TRIANGLES_SIGNAL_H
#define TRIANGLES_SIGNAL_H
#include <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
/**
* Lightweight multi-listener signal built on std::function.
*
* Two variants via partial specialization on a function signature:
* CSignal<void(Args...)> fan-out signal, operator() returns void.
* CSignal<R(Args...)> returns std::optional<R> from the most-recently
* connected slot (mirroring signals2's last_value
* policy with optional fallback when no slots).
*
* connect(slot) returns a Connection token; call .disconnect() to unsubscribe.
*
* Disconnect-by-bind-identity (signals2's compare-by-equivalent-bind trick) is
* intentionally not provided std::function lacks equality. Callers should
* keep the Connection token (or a CSignalConnections bag) and disconnect via it.
*/
template <typename Signature> class CSignal;
namespace signal_detail {
template <typename Slot>
struct SignalState
{
std::map<std::size_t, Slot> slots;
std::size_t next_id = 0;
};
template <typename Slot>
class ConnectionT
{
public:
ConnectionT() = default;
void disconnect()
{
if (auto sp = m_state.lock()) {
sp->slots.erase(m_id);
}
m_state.reset();
}
template <typename> friend class SignalBase;
ConnectionT(std::weak_ptr<SignalState<Slot>> state, std::size_t id)
: m_state(std::move(state)), m_id(id) {}
private:
std::weak_ptr<SignalState<Slot>> m_state;
std::size_t m_id = 0;
};
template <typename Slot>
class SignalBase
{
public:
using slot_type = Slot;
using Connection = ConnectionT<Slot>;
SignalBase() : m_state(std::make_shared<SignalState<Slot>>()) {}
SignalBase(const SignalBase&) = delete;
SignalBase& operator=(const SignalBase&) = delete;
Connection connect(slot_type slot)
{
std::size_t id = ++m_state->next_id;
m_state->slots.emplace(id, std::move(slot));
return Connection(m_state, id);
}
bool empty() const { return m_state->slots.empty(); }
protected:
std::shared_ptr<SignalState<Slot>> m_state;
};
} // namespace signal_detail
// Void-returning specialization: invoke every connected slot.
template <typename... Args>
class CSignal<void(Args...)> : public signal_detail::SignalBase<std::function<void(Args...)>>
{
public:
template <typename... CallArgs>
void operator()(CallArgs&&... args) const
{
// Snapshot lets slots mutate connections during invocation.
auto snapshot = this->m_state->slots;
for (auto& kv : snapshot) {
if (kv.second) kv.second(args...);
}
}
};
// Non-void specialization: invoke every slot, return the most-recent slot's
// result wrapped in optional (empty if no slots are connected).
template <typename R, typename... Args>
class CSignal<R(Args...)> : public signal_detail::SignalBase<std::function<R(Args...)>>
{
public:
template <typename... CallArgs>
std::optional<R> operator()(CallArgs&&... args) const
{
auto snapshot = this->m_state->slots;
std::optional<R> result;
for (auto& kv : snapshot) {
if (kv.second) result = kv.second(args...);
}
return result;
}
};
/**
* Holder for a heterogeneous list of signal connections that should all be
* released together (typical pattern: subscribe in ctor, drop on dtor).
*/
class CSignalConnections
{
public:
CSignalConnections() = default;
CSignalConnections(const CSignalConnections&) = delete;
CSignalConnections& operator=(const CSignalConnections&) = delete;
template <typename Conn>
void add(Conn conn)
{
auto holder = std::make_shared<Conn>(std::move(conn));
m_disconnectors.emplace_back([holder]() { holder->disconnect(); });
}
void disconnect_all()
{
for (auto& d : m_disconnectors) d();
m_disconnectors.clear();
}
~CSignalConnections() { disconnect_all(); }
private:
std::vector<std::function<void()>> m_disconnectors;
};
#endif // TRIANGLES_SIGNAL_H
+3 -3
View File
@@ -77,9 +77,9 @@ Notes:
// TODO: For buckets older than current, only need to store no. messages and hash in memory
boost::signals2::signal<void (SecMsgStored& inboxHdr)> NotifySecMsgInboxChanged;
boost::signals2::signal<void (SecMsgStored& outboxHdr)> NotifySecMsgOutboxChanged;
boost::signals2::signal<void ()> NotifySecMsgWalletUnlocked;
CSignal<void(SecMsgStored&)> NotifySecMsgInboxChanged;
CSignal<void(SecMsgStored&)> NotifySecMsgOutboxChanged;
CSignal<void()> NotifySecMsgWalletUnlocked;
bool fSecMsgEnabled = false;
+4 -3
View File
@@ -9,6 +9,7 @@
#include "net.h"
#include "db.h"
#include "signal.h"
#include "wallet.h"
#include "lz4/lz4.h"
@@ -41,13 +42,13 @@ extern bool fSecMsgEnabled;
class SecMsgStored;
// Inbox db changed, called with lock cs_smsgDB held.
extern boost::signals2::signal<void (SecMsgStored& inboxHdr)> NotifySecMsgInboxChanged;
extern CSignal<void(SecMsgStored&)> NotifySecMsgInboxChanged;
// Outbox db changed, called with lock cs_smsgDB held.
extern boost::signals2::signal<void (SecMsgStored& outboxHdr)> NotifySecMsgOutboxChanged;
extern CSignal<void(SecMsgStored&)> NotifySecMsgOutboxChanged;
// Wallet Unlocked, called after all messages received while locked have been processed.
extern boost::signals2::signal<void ()> NotifySecMsgWalletUnlocked;
extern CSignal<void()> NotifySecMsgWalletUnlocked;
class SecMsgBucket;
+19 -9
View File
@@ -13,6 +13,7 @@
#include "main.h"
#include "net.h"
#include "notificationqueue.h"
#include "signal.h"
#undef printf
#include <boost/asio.hpp>
@@ -25,6 +26,7 @@
#include <boost/asio/ssl.hpp>
#include <fstream>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <memory>
#include <list>
@@ -886,7 +888,7 @@ void ThreadRPCServer2(void* parg)
boost::system::error_code v6_only_error;
boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(io_service));
boost::signals2::signal<void ()> StopRequests;
CSignal<void()> StopRequests;
bool fListening = false;
std::string strerr;
@@ -902,10 +904,15 @@ void ThreadRPCServer2(void* parg)
acceptor->listen(socket_base::max_listen_connections);
RPCListen(acceptor, context, fUseSSL);
// Cancel outstanding listen-requests for this acceptor when shutting down
StopRequests.connect(signals2::slot<void ()>(
static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get())
.track(acceptor));
// Cancel outstanding listen-requests for this acceptor when shutting down.
// weak_ptr emulates signals2's .track(): if the acceptor has already been
// released by the time StopRequests fires, the slot is a no-op.
{
boost::weak_ptr<ip::tcp::acceptor> weak_acceptor(acceptor);
StopRequests.connect([weak_acceptor]() {
if (auto a = weak_acceptor.lock()) a->close();
});
}
fListening = true;
}
@@ -928,10 +935,13 @@ void ThreadRPCServer2(void* parg)
acceptor->listen(socket_base::max_listen_connections);
RPCListen(acceptor, context, fUseSSL);
// Cancel outstanding listen-requests for this acceptor when shutting down
StopRequests.connect(signals2::slot<void ()>(
static_cast<void (ip::tcp::acceptor::*)()>(&ip::tcp::acceptor::close), acceptor.get())
.track(acceptor));
// See note above on weak_ptr-based .track() emulation.
{
boost::weak_ptr<ip::tcp::acceptor> weak_acceptor(acceptor);
StopRequests.connect([weak_acceptor]() {
if (auto a = weak_acceptor.lock()) a->close();
});
}
fListening = true;
}
+15 -16
View File
@@ -6,11 +6,9 @@
#ifndef TRIANGLES_UI_INTERFACE_H
#define TRIANGLES_UI_INTERFACE_H
#include <boost/signals2/last_value.hpp>
#include <boost/signals2/signal.hpp>
#include <boost/bind/bind.hpp>
using namespace boost::placeholders;
#include "signal.h"
#include <optional>
#include <string>
#include <stdint.h>
@@ -65,40 +63,41 @@ public:
};
/** Show message box. */
boost::signals2::signal<void (const std::string& message, const std::string& caption, int style)> ThreadSafeMessageBox;
CSignal<void(const std::string& message, const std::string& caption, int style)> ThreadSafeMessageBox;
/** Ask the user whether they want to pay a fee or not. */
boost::signals2::signal<bool (int64_t nFeeRequired, const std::string& strCaption), boost::signals2::last_value<bool> > ThreadSafeAskFee;
CSignal<bool(int64_t nFeeRequired, const std::string& strCaption)> ThreadSafeAskFee;
/** Handle a URL passed at the command line. */
boost::signals2::signal<void (const std::string& strURI)> ThreadSafeHandleURI;
CSignal<void(const std::string& strURI)> ThreadSafeHandleURI;
/** Progress message during initialization. */
boost::signals2::signal<void (const std::string &message)> InitMessage;
CSignal<void(const std::string& message)> InitMessage;
/** Initiate client shutdown. */
boost::signals2::signal<void ()> QueueShutdown;
CSignal<void()> QueueShutdown;
/** Translate a message to the native language of the user. */
boost::signals2::signal<std::string (const char* psz)> Translate;
CSignal<std::string(const char* psz)> Translate;
/** Block chain changed. */
boost::signals2::signal<void ()> NotifyBlocksChanged;
CSignal<void()> NotifyBlocksChanged;
/** Number of network connections changed. */
boost::signals2::signal<void (int newNumConnections)> NotifyNumConnectionsChanged;
CSignal<void(int newNumConnections)> NotifyNumConnectionsChanged;
};
extern CClientUIInterface uiInterface;
/**
* Translation function: Call Translate signal on UI interface, which returns a boost::optional result.
* If no translation slot is registered, nothing is returned, and simply return the input.
* Translation function: Call Translate signal on UI interface, which returns
* an std::optional. If no translation slot is registered, fall back to the
* untranslated input.
*/
inline std::string _(const char* psz)
{
boost::optional<std::string> rv = uiInterface.Translate(psz);
return rv ? (*rv) : psz;
std::optional<std::string> rv = uiInterface.Translate(psz);
return rv ? *rv : psz;
}
#endif
+1 -1
View File
@@ -2231,7 +2231,7 @@ string CWallet::SendMoney(CScript scriptPubKey, int64_t nValue, std::string& sNa
return strError;
}
if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending...")))
if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending...")).value_or(false))
return "ABORTED";
if (!CommitTransaction(wtxNew, reservekey))
+2 -2
View File
@@ -334,12 +334,12 @@ public:
/** Address book entry changed.
* @note called with lock cs_wallet held.
*/
boost::signals2::signal<void (CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, ChangeType status)> NotifyAddressBookChanged;
CSignal<void(CWallet*, const CTxDestination&, const std::string&, bool, ChangeType)> NotifyAddressBookChanged;
/** Wallet transaction added, removed or updated.
* @note called with lock cs_wallet held.
*/
boost::signals2::signal<void (CWallet *wallet, const uint256 &hashTx, ChangeType status)> NotifyTransactionChanged;
CSignal<void(CWallet*, const uint256&, ChangeType)> NotifyTransactionChanged;
};
/** A key allocated from the key pool. */