From f7aa7730059f936e9d3b355156b63a116262cf12 Mon Sep 17 00:00:00 2001 From: Javier Serrano Polo Date: Sun, 3 Jul 2016 14:09:41 +0200 Subject: [PATCH] Added LocklessList and use it for new play handles --- include/LocklessList.h | 86 ++++++++++++++++++++++++++++++++++++++++++ include/Mixer.h | 4 +- src/core/Mixer.cpp | 42 ++++++++++++++------- 3 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 include/LocklessList.h diff --git a/include/LocklessList.h b/include/LocklessList.h new file mode 100644 index 00000000000..f97b219be0a --- /dev/null +++ b/include/LocklessList.h @@ -0,0 +1,86 @@ +/* + * LocklessList.h - list with lockless push and pop + * + * Copyright (c) 2016 Javier Serrano Polo + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef LOCKLESS_LIST_H +#define LOCKLESS_LIST_H + +#include + +template +class LocklessList +{ +public: + struct Element + { + T value; + Element * next; + } ; + + void push( T value ) + { + Element * e = new Element; + e->value = value; + + do + { +#if QT_VERSION >= 0x050000 + e->next = m_first.loadAcquire(); +#else + e->next = m_first; +#endif + } + while( !m_first.testAndSetOrdered( e->next, e ) ); + } + + Element * popList() + { + return m_first.fetchAndStoreOrdered( NULL ); + } + + Element * first() + { +#if QT_VERSION >= 0x050000 + return m_first.loadAcquire(); +#else + return m_first; +#endif + } + + void setFirst( Element * e ) + { +#if QT_VERSION >= 0x050000 + m_first.storeRelease( e ); +#else + m_first = e; +#endif + } + + +private: + QAtomicPointer m_first; + +} ; + + +#endif diff --git a/include/Mixer.h b/include/Mixer.h index f3c849c3ee1..1621457136e 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -37,6 +37,7 @@ #include "lmms_basics.h" +#include "LocklessList.h" #include "Note.h" #include "fifo_buffer.h" #include "MixerProfiler.h" @@ -379,7 +380,8 @@ class EXPORT Mixer : public QObject // playhandle stuff PlayHandleList m_playHandles; - PlayHandleList m_newPlayHandles; // place where new playhandles are added temporarily + // place where new playhandles are added temporarily + LocklessList m_newPlayHandles; ConstPlayHandleList m_playHandlesToRemove; diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index de89d2bd28d..9cbcb2d737d 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -61,6 +61,9 @@ +typedef LocklessList::Element LocklessListElement; + + static __thread bool s_renderingThread; @@ -412,8 +415,13 @@ const surroundSampleFrame * Mixer::renderNextBuffer() song->processNextBuffer(); // add all play-handles that have to be added - m_playHandles += m_newPlayHandles; - m_newPlayHandles.clear(); + for( LocklessListElement * e = m_newPlayHandles.popList(); e; ) + { + m_playHandles += e->value; + LocklessListElement * next = e->next; + delete e; + e = next; + } // STAGE 1: run and render all play handles MixerWorkerThread::fillJobQueue( m_playHandles ); @@ -635,10 +643,8 @@ bool Mixer::addPlayHandle( PlayHandle* handle ) { if( criticalXRuns() == false ) { - requestChangeInModel(); - m_newPlayHandles.append( handle ); - handle->audioPort()->addPlayHandle( handle ); - doneChangeInModel(); + m_newPlayHandles.push( handle ); + handle->audioPort()->addPlayHandle( handle ); return true; } @@ -664,16 +670,26 @@ void Mixer::removePlayHandle( PlayHandle * _ph ) bool removedFromList = false; // Check m_newPlayHandles first because doing it the other way around // creates a race condition - PlayHandleList::Iterator it = - qFind( m_newPlayHandles.begin(), - m_newPlayHandles.end(), _ph ); - if( it != m_newPlayHandles.end() ) + for( LocklessListElement * e = m_newPlayHandles.first(), + * ePrev = NULL; e; ePrev = e, e = e->next ) { - m_newPlayHandles.erase( it ); - removedFromList = true; + if( e->value == _ph ) + { + if( ePrev ) + { + ePrev->next = e->next; + } + else + { + m_newPlayHandles.setFirst( e->next ); + } + delete e; + removedFromList = true; + break; + } } // Now check m_playHandles - it = qFind( m_playHandles.begin(), + PlayHandleList::Iterator it = qFind( m_playHandles.begin(), m_playHandles.end(), _ph ); if( it != m_playHandles.end() ) {