From 7a158681cd9d887494b91e23aca212564c5e7705 Mon Sep 17 00:00:00 2001 From: gvnnz Date: Sat, 27 Aug 2022 13:45:43 +0200 Subject: [PATCH] Multi-producer/multi-consumer support in EventDispatcher's queue With moodycamel::ConcurrentQueue. --- .gitmodules | 3 +++ src/core/engine.cpp | 14 +++++++------- src/core/eventDispatcher.cpp | 11 ++++++----- src/core/eventDispatcher.h | 36 ++++++++++++++++-------------------- src/deps/concurrentqueue | 1 + src/glue/events.cpp | 14 +++----------- 6 files changed, 36 insertions(+), 43 deletions(-) create mode 160000 src/deps/concurrentqueue diff --git a/.gitmodules b/.gitmodules index e1929994..2f297492 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "src/deps/fltk"] path = src/deps/fltk url = https://github.com/fltk/fltk.git +[submodule "src/deps/concurrentqueue"] + path = src/deps/concurrentqueue + url = https://github.com/cameron314/concurrentqueue.git diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 1cb9001c..89ca9a13 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -66,16 +66,16 @@ Engine::Engine() #ifdef WITH_AUDIO_JACK jackSynchronizer.onJackRewind = [this]() { - eventDispatcher.pumpMidiEvent({EventDispatcher::EventType::SEQUENCER_REWIND_JACK}); + eventDispatcher.pumpEvent({EventDispatcher::EventType::SEQUENCER_REWIND_JACK}); }; jackSynchronizer.onJackChangeBpm = [this](float bpm) { - eventDispatcher.pumpMidiEvent({EventDispatcher::EventType::SEQUENCER_BPM_JACK, 0, 0, bpm}); + eventDispatcher.pumpEvent({EventDispatcher::EventType::SEQUENCER_BPM_JACK, 0, 0, bpm}); }; jackSynchronizer.onJackStart = [this]() { - eventDispatcher.pumpMidiEvent({EventDispatcher::EventType::SEQUENCER_START_JACK}); + eventDispatcher.pumpEvent({EventDispatcher::EventType::SEQUENCER_START_JACK}); }; jackSynchronizer.onJackStop = [this]() { - eventDispatcher.pumpMidiEvent({EventDispatcher::EventType::SEQUENCER_STOP_JACK}); + eventDispatcher.pumpEvent({EventDispatcher::EventType::SEQUENCER_STOP_JACK}); }; #endif @@ -99,7 +99,7 @@ Engine::Engine() midiDispatcher.onDispatch = [this](EventDispatcher::EventType event, Action action) { /* Notify Event Dispatcher when a MIDI signal is received. */ - eventDispatcher.pumpMidiEvent({event, 0, 0, action}); + eventDispatcher.pumpEvent({event, 0, 0, action}); }; midiDispatcher.onEventReceived = [this]() { @@ -111,12 +111,12 @@ Engine::Engine() event to the Event Dispatcher, rather than invoking the callback directly. This is done on purpose: the callback might (and surely will) contain blocking stuff from model:: that the realtime thread cannot perform directly. */ - eventDispatcher.pumpUIevent({EventDispatcher::EventType::MIXER_SIGNAL_CALLBACK}); + eventDispatcher.pumpEvent({EventDispatcher::EventType::MIXER_SIGNAL_CALLBACK}); }; mixer.onEndOfRecording = [this]() { /* Same rationale as above, for the end-of-recording callback. */ - eventDispatcher.pumpUIevent({EventDispatcher::EventType::MIXER_END_OF_REC_CALLBACK}); + eventDispatcher.pumpEvent({EventDispatcher::EventType::MIXER_END_OF_REC_CALLBACK}); }; channelManager.onChannelsAltered = [this]() { diff --git a/src/core/eventDispatcher.cpp b/src/core/eventDispatcher.cpp index 43177dc6..16a1a81b 100644 --- a/src/core/eventDispatcher.cpp +++ b/src/core/eventDispatcher.cpp @@ -37,6 +37,7 @@ EventDispatcher::EventDispatcher() , onProcessSequencer(nullptr) , onMixerSignalCallback(nullptr) , onMixerEndOfRecCallback(nullptr) +, m_eventQueue(G_MAX_DISPATCHER_EVENTS) { } @@ -49,8 +50,10 @@ void EventDispatcher::start() /* -------------------------------------------------------------------------- */ -void EventDispatcher::pumpUIevent(Event e) { UIevents.push(e); } -void EventDispatcher::pumpMidiEvent(Event e) { MidiEvents.push(e); } +bool EventDispatcher::pumpEvent(const Event& e) +{ + return m_eventQueue.try_enqueue(e); +} /* -------------------------------------------------------------------------- */ @@ -97,9 +100,7 @@ void EventDispatcher::process() m_eventBuffer.clear(); Event e; - while (UIevents.pop(e)) - m_eventBuffer.push_back(e); - while (MidiEvents.pop(e)) + while (m_eventQueue.try_dequeue(e)) m_eventBuffer.push_back(e); if (m_eventBuffer.size() == 0) diff --git a/src/core/eventDispatcher.h b/src/core/eventDispatcher.h index 77bd4799..2dbd07ac 100644 --- a/src/core/eventDispatcher.h +++ b/src/core/eventDispatcher.h @@ -27,21 +27,21 @@ #ifndef G_EVENT_DISPATCHER_H #define G_EVENT_DISPATCHER_H +#include "core/actions/action.h" #include "core/const.h" -#include "core/queue.h" #include "core/ringBuffer.h" #include "core/types.h" #include "core/worker.h" -#include "src/core/actions/action.h" +#include "deps/concurrentqueue/concurrentqueue.h" #include #include #include #include /* giada::m::EventDispatcher -Takes events from the two queues (MIDI and UI) filled by c::events and turns -them into actual changes in the data model. The EventDispatcher runs in a -separate worker thread. */ +Takes events from the a lock-free queue filled by c::events and turns them into +actual changes in the data model. The EventDispatcher runs in a separate worker +thread. */ namespace giada::m { @@ -92,11 +92,9 @@ public: }; /* EventBuffer - Alias for a RingBuffer containing events to be sent to engine. The double - size is due to the presence of two distinct Queues for collecting events - coming from other threads. See below. */ + Alias for a RingBuffer containing events to be sent to engine. */ - using EventBuffer = RingBuffer; + using EventBuffer = RingBuffer; EventDispatcher(); @@ -105,17 +103,10 @@ public: void start(); - void pumpUIevent(Event e); - void pumpMidiEvent(Event e); + /* pumpEvent + Inserts a new event in the event queue. Returns false if the queue is full. */ - /* Event queues - Collect events coming from the UI or MIDI devices. Our poor man's Queue is a - single-producer/single-consumer one, so we need two queues for two writers. - TODO - let's add a multi-producer queue sooner or later! */ - /*TODO - make them private*/ - - Queue UIevents; - Queue MidiEvents; + bool pumpEvent(const Event&); /* on[...] Callbacks fired when something happens in the Event Dispatcher. */ @@ -138,9 +129,14 @@ private: /* m_eventBuffer Buffer of events sent to channels for event parsing. This is filled with - Events coming from the two event queues.*/ + Events coming from the event queue.*/ EventBuffer m_eventBuffer; + + /* m_eventQueue + Collects events coming from the UI or MIDI devices. */ + + moodycamel::ConcurrentQueue m_eventQueue; }; } // namespace giada::m diff --git a/src/deps/concurrentqueue b/src/deps/concurrentqueue new file mode 160000 index 00000000..65d69709 --- /dev/null +++ b/src/deps/concurrentqueue @@ -0,0 +1 @@ +Subproject commit 65d6970912fc3f6bb62d80edf95ca30e0df85137 diff --git a/src/glue/events.cpp b/src/glue/events.cpp index 8ba7fe91..0398e567 100644 --- a/src/glue/events.cpp +++ b/src/glue/events.cpp @@ -66,22 +66,14 @@ namespace { void pushEvent_(m::EventDispatcher::Event e, Thread t) { - bool res = true; - if (t == Thread::MAIN) + const bool res = g_engine.eventDispatcher.pumpEvent(e); + + if (t == Thread::MIDI) { - res = g_engine.eventDispatcher.UIevents.push(e); - } - else if (t == Thread::MIDI) - { - res = g_engine.eventDispatcher.MidiEvents.push(e); u::gui::ScopedLock lock; if (e.channelId != 0) g_ui.mainWindow->keyboard->notifyMidiIn(e.channelId); } - else - { - assert(false); - } if (!res) G_DEBUG("[events] Queue full!", );