// Copyright (C) 2020 Jérôme Leclercq // This file is part of the "Nazara Engine - Core module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include #include namespace Nz { /*! * \ingroup core * \class Nz::Signal * \brief Core class that represents a signal, a list of objects waiting for its message */ /*! * \brief Constructs a Signal object by default */ template Signal::Signal() : m_slotIterator(0) { } /*! * \brief Constructs a Signal object by default * * \remark It doesn't make sense to copy a signal, this is only available for convenience to allow compiler-generated copy constructors */ template Signal::Signal(const Signal&) : Signal() { } /*! * \brief Constructs a Signal object by move semantic * * \param signal Signal to move in this */ template Signal::Signal(Signal&& signal) noexcept { operator=(std::move(signal)); } /*! * \brief Clears the list of actions attached to the signal */ template void Signal::Clear() { m_slots.clear(); m_slotIterator = 0; } /*! * \brief Connects a function to the signal * \return Connection attached to the signal * * \param func Non-member function */ template typename Signal::Connection Signal::Connect(const Callback& func) { return Connect(Callback(func)); } /*! * \brief Connects a function to the signal * \return Connection attached to the signal * * \param func Non-member function */ template typename Signal::Connection Signal::Connect(Callback&& func) { NazaraAssert(func, "Invalid function"); // Since we're incrementing the slot vector size, we need to replace our iterator at the end // (Except when we are iterating on the signal) bool resetIt = (m_slotIterator >= m_slots.size()); auto tempPtr = std::make_shared(this); tempPtr->callback = std::move(func); tempPtr->index = m_slots.size(); m_slots.emplace_back(std::move(tempPtr)); if (resetIt) m_slotIterator = m_slots.size(); //< Replace the iterator to the end return Connection(m_slots.back()); } /*! * \brief Connects a member function and its object to the signal * \return Connection attached to the signal * * \param object Object to send the message * \param method Member function */ template template typename Signal::Connection Signal::Connect(O& object, void (O::*method) (Args...)) { return Connect([&object, method] (Args&&... args) { return (object .* method) (std::forward(args)...); }); } /*! * \brief Connects a member function and its object to the signal * \return Connection attached to the signal * * \param object Object to send the message * \param method Member function */ template template typename Signal::Connection Signal::Connect(O* object, void (O::*method)(Args...)) { return Connect([object, method] (Args&&... args) { return (object ->* method) (std::forward(args)...); }); } /*! * \brief Connects a member function and its object to the signal * \return Connection attached to the signal * * \param object Object to send the message * \param method Member function */ template template typename Signal::Connection Signal::Connect(const O& object, void (O::*method) (Args...) const) { return Connect([&object, method] (Args&&... args) { return (object .* method) (std::forward(args)...); }); } /*! * \brief Connects a member function and its object to the signal * \return Connection attached to the signal * * \param object Object to send the message * \param method Member function */ template template typename Signal::Connection Signal::Connect(const O* object, void (O::*method)(Args...) const) { return Connect([object, method] (Args&&... args) { return (object ->* method) (std::forward(args)...); }); } /*! * \brief Applies the list of arguments to every callback functions * * \param args Arguments to send with the message */ template void Signal::operator()(Args... args) const { for (m_slotIterator = 0; m_slotIterator < m_slots.size(); ++m_slotIterator) m_slots[m_slotIterator]->callback(args...); } /*! * \brief Doesn't do anything * \return A reference to this * * \remark This is only for convenience to allow compiled-generated assignation operator */ template Signal& Signal::operator=(const Signal&) { return *this; } /*! * \brief Moves the signal into this * \return A reference to this * * \param signal Signal to move in this */ template Signal& Signal::operator=(Signal&& signal) noexcept { m_slots = std::move(signal.m_slots); m_slotIterator = signal.m_slotIterator; // We need to update the signal pointer inside of each slot for (SlotPtr& slot : m_slots) slot->signal = this; return *this; } /*! * \brief Disconnects a listener from this signal * * \param slot Pointer to the ith listener of the signal * * \remark Produces a NazaraAssert if slot is invalid (nullptr) * \remark Produces a NazaraAssert if index of slot is invalid * \remark Produces a NazaraAssert if slot is not attached to this signal */ template void Signal::Disconnect(const SlotPtr& slot) noexcept { NazaraAssert(slot, "Invalid slot pointer"); NazaraAssert(slot->index < m_slots.size(), "Invalid slot index"); NazaraAssert(slot->signal == this, "Slot is not attached to this signal"); // "Swap this slot with the last one and pop" idiom // This will preserve slot indexes // Can we safely "remove" this slot? if (m_slotIterator >= (m_slots.size() - 1) || slot->index > m_slotIterator) { // Yes we can SlotPtr& newSlot = m_slots[slot->index]; newSlot = std::move(m_slots.back()); newSlot->index = slot->index; //< Update the moved slot index before resizing (in case it's the last one) } else { // Nope, let's be tricky SlotPtr& current = m_slots[m_slotIterator]; SlotPtr& newSlot = m_slots[slot->index]; newSlot = std::move(current); newSlot->index = slot->index; //< Update the moved slot index current = std::move(m_slots.back()); current->index = m_slotIterator; //< Update the moved slot index --m_slotIterator; } // Pop the last entry (from where we moved our slot) m_slots.pop_back(); } /*! * \class Nz::Signal::Connection * \brief Core class that represents a connection attached to a signal */ /*! * \brief Constructs a Signal::Connection object with by move semantic * * \param connection Connection object to move */ template Signal::Connection::Connection(Connection&& connection) noexcept : m_ptr(std::move(connection.m_ptr)) { connection.m_ptr.reset(); //< Fuck you GCC 4.9 } /*! * \brief Constructs a Signal::Connection object with a slot * * \param slot Slot of the listener */ template Signal::Connection::Connection(const SlotPtr& slot) : m_ptr(slot) { } /*! * \brief Connects to a signal with arguments * * \param signal New signal to listen * \param args Arguments for the signal */ template template void Signal::Connection::Connect(BaseClass& signal, ConnectArgs&&... args) { operator=(signal.Connect(std::forward(args)...)); } /*! * \brief Disconnects the connection from the signal */ template void Signal::Connection::Disconnect() noexcept { if (SlotPtr ptr = m_ptr.lock()) ptr->signal->Disconnect(ptr); } /*! * \brief Checks whether the connection is still active with the signal * \return true if signal is still active */ template bool Signal::Connection::IsConnected() const { return !m_ptr.expired(); } /*! * \brief Constructs a Signal::ConnectionGuard object by move semantic * * \param connection Connection to move */ template typename Signal::Connection& Signal::Connection::operator=(Connection&& connection) noexcept { m_ptr = std::move(connection.m_ptr); connection.m_ptr.reset(); //< Fuck you GCC 4.9 return *this; } /*! * \class Nz::Signal::ConnectionGuard * \brief Core class that represents a RAII for a connection attached to a signal */ /*! * \brief Constructs a Signal::ConnectionGuard object with a connection * * \param connection Connection for the scope */ template Signal::ConnectionGuard::ConnectionGuard(const Connection& connection) : m_connection(connection) { } /*! * \brief Constructs a Signal::ConnectionGuard object with a connection by move semantic * * \param connection Connection for the scope */ template Signal::ConnectionGuard::ConnectionGuard(Connection&& connection) : m_connection(std::move(connection)) { } /*! * \brief Destructs the object and disconnects the connection */ template Signal::ConnectionGuard::~ConnectionGuard() { m_connection.Disconnect(); } /*! * \brief Connects to a signal with arguments * * \param signal New signal to listen * \param args Arguments for the signal */ template template void Signal::ConnectionGuard::Connect(BaseClass& signal, ConnectArgs&&... args) { m_connection.Disconnect(); m_connection.Connect(signal, std::forward(args)...); } /*! * \brief Disconnects the connection from the signal */ template void Signal::ConnectionGuard::Disconnect() noexcept { m_connection.Disconnect(); } /*! * \brief Gets the connection attached to the signal * \return Connection of the signal */ template typename Signal::Connection& Signal::ConnectionGuard::GetConnection() { return m_connection; } /*! * \brief Checks whether the connection is still active with the signal * \return true if signal is still active */ template bool Signal::ConnectionGuard::IsConnected() const { return m_connection.IsConnected(); } /*! * \brief Assigns the connection into this * \return A reference to this * * \param connection Connection to assign into this */ template typename Signal::ConnectionGuard& Signal::ConnectionGuard::operator=(const Connection& connection) { m_connection.Disconnect(); m_connection = connection; return *this; } /*! * \brief Moves the Connection into this * \return A reference to this * * \param connection Connection to move in this */ template typename Signal::ConnectionGuard& Signal::ConnectionGuard::operator=(Connection&& connection) { if (&connection != this) { m_connection.Disconnect(); m_connection = std::move(connection); } return *this; } /*! * \brief Moves the ConnectionGuard into this * \return A reference to this * * \param connection ConnectionGuard to move in this */ template typename Signal::ConnectionGuard& Signal::ConnectionGuard::operator=(ConnectionGuard&& connection) noexcept { if (&connection != this) { m_connection.Disconnect(); m_connection = std::move(connection.m_connection); } return *this; } } #include