Skip to content

Interconnect - Slots are not called according to their record order #168

@sq-gilles

Description

@sq-gilles

Hi,

Using the signal/slot library of Corrade, it appears that when one attaches many slots to same signal, the slots are not called in any particular order. In particular, there are not called in the record order.

We can easily see that by tweaking the Corrade/Interconnect/Test/LibraryTest.cpp

void LibraryTest::test() {
    EmitterLibrary e;
 
    int orderedFired = 1;
    int expected = orderedFired;
    for (int i : {1, 2, 3, 4, 5}) {
        connect(e, &EmitterLibrary::fireInline,
                [&orderedFired, i]() { orderedFired = (orderedFired + i) * (i + 1); });
        // non commutative operation
        expected = (expected + i) * (i + 1);
    }

    e.fireInline();
    CORRADE_COMPARE(orderedFired, expected); // will certainly fail
}

Controlling the slot invocation order is mandatory when we want to manage events layer by layer (e.g. when managing io events in a scene graph).
For example Qt (that seems to have inspired this api) ensures this behavior https://doc.qt.io/qt-6/signalsandslots.html#signals

If several slots are connected to one signal, the slots will be executed one after the other, in the order they have been connected, when the signal is emitted.

This is due to the usage of std::unordered_multimap in src/Corrade/Interconnect/Emitter.h:510. In fact, there is no guarantee on the order for "key-value pairs whose keys compare equivalent".

A very simple fix would be to replace the unordered_multimap by a multimap. Indeed, multimap ensures the order of equivalent keys is the same as their insertion order (https://en.cppreference.com/w/cpp/container/multimap)

diff --git a/src/Corrade/Interconnect/Connection.h b/src/Corrade/Interconnect/Connection.h
index 86b0b4d8..4e660d8b 100644
--- a/src/Corrade/Interconnect/Connection.h
+++ b/src/Corrade/Interconnect/Connection.h
@@ -90,6 +90,12 @@ namespace Implementation {
                 return !operator==(other);
             }
 
+            bool operator<(const SignalData& other) const {
+                for(std::size_t i = 0; i != FunctionPointerSize; ++i)
+                    if(data[i] < other.data[i]) return true;
+                return false;
+            }
+
         private:
             /* https://bugzilla.gnome.org/show_bug.cgi?id=776986 */
             #ifndef DOXYGEN_GENERATING_OUTPUT
diff --git a/src/Corrade/Interconnect/Emitter.h b/src/Corrade/Interconnect/Emitter.h
index ed151032..a02955cb 100644
--- a/src/Corrade/Interconnect/Emitter.h
+++ b/src/Corrade/Interconnect/Emitter.h
@@ -34,6 +34,7 @@
 #include <cstdint>
 #include <type_traits>
 #include <unordered_map>
+#include <map>
 #include <utility>
 
 #include "Corrade/Interconnect/Connection.h"
@@ -506,7 +507,7 @@ class CORRADE_INTERCONNECT_EXPORT Emitter {
 
         void disconnectInternal(const Implementation::SignalData& signal);
 
-        std::unordered_multimap<Implementation::SignalData, Implementation::ConnectionData, Implementation::SignalDataHash> _connections;
+        std::multimap<Implementation::SignalData, Implementation::ConnectionData> _connections;
         std::uint32_t _lastHandledSignal;
         bool _connectionsChanged;
 };

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status

    TODO

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions