diff options
Diffstat (limited to '')
-rw-r--r-- | svl/source/notify/SfxBroadcaster.cxx | 145 | ||||
-rw-r--r-- | svl/source/notify/broadcast.cxx | 254 | ||||
-rw-r--r-- | svl/source/notify/isethint.cxx | 33 | ||||
-rw-r--r-- | svl/source/notify/listener.cxx | 89 | ||||
-rw-r--r-- | svl/source/notify/lstner.cxx | 163 |
5 files changed, 684 insertions, 0 deletions
diff --git a/svl/source/notify/SfxBroadcaster.cxx b/svl/source/notify/SfxBroadcaster.cxx new file mode 100644 index 000000000..dbd1ab3a7 --- /dev/null +++ b/svl/source/notify/SfxBroadcaster.cxx @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <svl/SfxBroadcaster.hxx> + +#include <svl/hint.hxx> +#include <svl/lstner.hxx> +#include <tools/debug.hxx> + +#include <algorithm> +#include <cassert> +#include <vector> + +// broadcast immediately + +void SfxBroadcaster::Broadcast(const SfxHint& rHint) +{ + // notify all registered listeners exactly once + for (size_t i = 0; i < m_Listeners.size(); ++i) + { + SfxListener* const pListener = m_Listeners[i]; + if (pListener) + pListener->Notify(*this, rHint); + } +} + +// unregister all listeners + +SfxBroadcaster::~SfxBroadcaster() COVERITY_NOEXCEPT_FALSE +{ + Broadcast(SfxHint(SfxHintId::Dying)); + + // remove all still registered listeners + for (size_t i = 0; i < m_Listeners.size(); ++i) + { + SfxListener* const pListener = m_Listeners[i]; + if (pListener) + pListener->RemoveBroadcaster_Impl(*this); + } +} + +// copy ctor of class SfxBroadcaster + +SfxBroadcaster::SfxBroadcaster(const SfxBroadcaster& rOther) +{ + for (size_t i = 0; i < rOther.m_Listeners.size(); ++i) + { + SfxListener* const pListener = rOther.m_Listeners[i]; + if (pListener) + pListener->StartListening(*this); + } +} + +// add a new SfxListener to the list + +void SfxBroadcaster::AddListener(SfxListener& rListener) +{ + DBG_TESTSOLARMUTEX(); + if (m_RemovedPositions.empty()) + { + m_Listeners.push_back(&rListener); + } + else + { + size_t targetPosition = m_RemovedPositions.back(); + m_RemovedPositions.pop_back(); + assert(m_Listeners[targetPosition] == nullptr); + m_Listeners[targetPosition] = &rListener; + } +} + +// forward a notification to all registered listeners + +void SfxBroadcaster::Forward(SfxBroadcaster& rBC, const SfxHint& rHint) +{ + for (size_t i = 0; i < m_Listeners.size(); ++i) + { + SfxListener* const pListener = m_Listeners[i]; + if (pListener) + pListener->Notify(rBC, rHint); + } +} + +// remove one SfxListener from the list + +void SfxBroadcaster::RemoveListener(SfxListener& rListener) +{ + DBG_TESTSOLARMUTEX(); + + // First, check the slots either side of the last removed slot, makes a significant + // difference when the list is large. + int positionOfRemovedElement = -1; + if (!m_RemovedPositions.empty()) + { + auto i = m_RemovedPositions.back(); + if (i < m_Listeners.size() - 2 && m_Listeners[i + 1] == &rListener) + { + positionOfRemovedElement = i + 1; + } + else if (i > 0 && m_Listeners[i - 1] == &rListener) + { + positionOfRemovedElement = i - 1; + } + } + // then scan the whole list if we didn't find it + if (positionOfRemovedElement == -1) + { + auto aIter = std::find(m_Listeners.begin(), m_Listeners.end(), &rListener); + positionOfRemovedElement = std::distance(m_Listeners.begin(), aIter); + } + // DO NOT erase the listener, set the pointer to 0 + // because the current continuation may contain this->Broadcast + m_Listeners[positionOfRemovedElement] = nullptr; + m_RemovedPositions.push_back(positionOfRemovedElement); +} + +bool SfxBroadcaster::HasListeners() const { return (GetListenerCount() != 0); } + +size_t SfxBroadcaster::GetListenerCount() const +{ + assert(m_Listeners.size() >= m_RemovedPositions.size()); + return m_Listeners.size() - m_RemovedPositions.size(); +} + +size_t SfxBroadcaster::GetSizeOfVector() const { return m_Listeners.size(); } + +SfxListener* SfxBroadcaster::GetListener(size_t nNo) const { return m_Listeners[nNo]; } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/notify/broadcast.cxx b/svl/source/notify/broadcast.cxx new file mode 100644 index 000000000..042d1daca --- /dev/null +++ b/svl/source/notify/broadcast.cxx @@ -0,0 +1,254 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <svl/broadcast.hxx> +#include <svl/listener.hxx> +#include <svl/hint.hxx> +#include <o3tl/safeint.hxx> +#include <cassert> +#include <algorithm> + +/** + Design Notes + ------------------------------- + + This class is extremely heavily used - we can have millions of broadcasters and listeners and we can also + have a broadcaster that has a million listeners. + + So we do a number of things + (*) use a cache-dense listener list (std::vector) because caching effects dominate a lot of operations + (*) use a sorted list to speed up find operations + (*) only sort the list when we absolutely have to, to speed up sequential add/remove operations + (*) defer removing items from the list by (ab)using the last bit of the pointer + + Also we have some complications around destruction because + (*) we broadcast a message to indicate that we are destructing + (*) which can trigger arbitrality complicated behaviour, including + (*) adding a removing things from the in-destruction object! + +*/ + +static bool isDeletedPtr(SvtListener* p) +{ + /** mark deleted entries by toggling the last bit,which is effectively unused, since the struct we point + * to is at least 16-bit aligned. This allows the binary search to continue working even when we have + * deleted entries */ +#if SAL_TYPES_SIZEOFPOINTER == 4 + return (reinterpret_cast<sal_uInt32>(p) & 0x01) == 0x01; +#else + return (reinterpret_cast<sal_uInt64>(p) & 0x01) == 0x01; +#endif +} + +static void markDeletedPtr(SvtListener*& rp) +{ +#if SAL_TYPES_SIZEOFPOINTER == 4 + reinterpret_cast<sal_uInt32&>(rp) |= 0x01; +#else + reinterpret_cast<sal_uInt64&>(rp) |= 0x01; +#endif +} + +static void sortListeners(std::vector<SvtListener*>& listeners, size_t firstUnsorted) +{ + // Add() only appends new values, so often the container will be sorted expect for one + // or few last items. For larger containers it is much more efficient to just handle + // the unsorted part. + auto sortedEnd = firstUnsorted == 0 + ? std::is_sorted_until(listeners.begin(),listeners.end()) + : listeners.begin() + firstUnsorted; + if( listeners.end() - sortedEnd == 1 ) + { // Just one item, insert it in the right place. + SvtListener* item = listeners.back(); + listeners.pop_back(); + listeners.insert( std::upper_bound( listeners.begin(), listeners.end(), item ), item ); + } + else if( o3tl::make_unsigned( sortedEnd - listeners.begin()) > listeners.size() * 3 / 4 ) + { // Sort the unsorted part and then merge. + std::sort( sortedEnd, listeners.end()); + std::inplace_merge( listeners.begin(), sortedEnd, listeners.end()); + } + else + { + std::sort(listeners.begin(), listeners.end()); + } +} + +void SvtBroadcaster::Normalize() const +{ + // clear empty slots first, because then we often have to do very little sorting + if (mnEmptySlots) + { + maListeners.erase( + std::remove_if(maListeners.begin(), maListeners.end(), [] (SvtListener* p) { return isDeletedPtr(p); }), + maListeners.end()); + mnEmptySlots = 0; + } + + if (mnListenersFirstUnsorted != static_cast<sal_Int32>(maListeners.size())) + { + sortListeners(maListeners, mnListenersFirstUnsorted); + mnListenersFirstUnsorted = maListeners.size(); + } + + if (!mbDestNormalized) + { + sortListeners(maDestructedListeners, 0); + mbDestNormalized = true; + } +} + +void SvtBroadcaster::Add( SvtListener* p ) +{ + assert(!mbDisposing && "called inside my own destructor?"); + assert(!mbAboutToDie && "called after PrepareForDestruction()?"); + if (mbDisposing || mbAboutToDie) + return; + // Avoid normalizing if the item appended keeps the container sorted. + auto nRealSize = static_cast<sal_Int32>(maListeners.size() - mnEmptySlots); + auto bSorted = mnListenersFirstUnsorted == nRealSize; + if (maListeners.empty() || (bSorted && maListeners.back() <= p)) + { + ++mnListenersFirstUnsorted; + maListeners.push_back(p); + return; + } + // see if we can stuff it into an empty slot, which succeeds surprisingly often in + // some calc workloads where it removes and then re-adds the same listener + if (mnEmptySlots && bSorted) + { + auto it = std::lower_bound(maListeners.begin(), maListeners.end(), p); + if (it != maListeners.end() && isDeletedPtr(*it)) + { + *it = p; + ++mnListenersFirstUnsorted; + --mnEmptySlots; + return; + } + } + maListeners.push_back(p); +} + +void SvtBroadcaster::Remove( SvtListener* p ) +{ + if (mbDisposing) + return; + + if (mbAboutToDie) + { + // only reset mbDestNormalized if we are going to become unsorted + if (!maDestructedListeners.empty() && maDestructedListeners.back() > p) + mbDestNormalized = false; + maDestructedListeners.push_back(p); + return; + } + + // We only need to fully normalize if one or more Add()s have been performed that make the array unsorted. + auto nRealSize = static_cast<sal_Int32>(maListeners.size() - mnEmptySlots); + if (mnListenersFirstUnsorted != nRealSize || (maListeners.size() > 1024 && maListeners.size() / nRealSize > 16)) + Normalize(); + + auto it = std::lower_bound(maListeners.begin(), maListeners.end(), p); + assert (it != maListeners.end() && *it == p); + if (it != maListeners.end() && *it == p) + { + markDeletedPtr(*it); + ++mnEmptySlots; + --mnListenersFirstUnsorted; + } + + if (!HasListeners()) + ListenersGone(); +} + +SvtBroadcaster::SvtBroadcaster( const SvtBroadcaster &rBC ) : + mnEmptySlots(0), mnListenersFirstUnsorted(0), + mbAboutToDie(false), mbDisposing(false), + mbDestNormalized(true) +{ + assert(!rBC.mbAboutToDie && "copying an object marked with PrepareForDestruction()?"); + assert(!rBC.mbDisposing && "copying an object that is in its destructor?"); + + rBC.Normalize(); // so that insert into ourself is in-order, and therefore we do not need to Normalize() + maListeners.reserve(rBC.maListeners.size()); + for (SvtListener* p : rBC.maListeners) + p->StartListening(*this); // this will call back into this->Add() +} + +SvtBroadcaster::~SvtBroadcaster() +{ + mbDisposing = true; + Broadcast( SfxHint(SfxHintId::Dying) ); + + Normalize(); + + // now when both lists are sorted, we can linearly unregister all + // listeners, with the exception of those that already asked to be removed + // during their own destruction + ListenersType::const_iterator dest(maDestructedListeners.begin()); + for (auto& rpListener : maListeners) + { + // skip the destructed ones + while (dest != maDestructedListeners.end() && (*dest < rpListener)) + ++dest; + + if (dest == maDestructedListeners.end() || *dest != rpListener) + rpListener->BroadcasterDying(*this); + } +} + +void SvtBroadcaster::Broadcast( const SfxHint &rHint ) +{ + Normalize(); + + ListenersType::const_iterator dest(maDestructedListeners.begin()); + ListenersType aListeners(maListeners); // this copy is important to avoid erasing entries while iterating + for (auto& rpListener : aListeners) + { + // skip the destructed ones + while (dest != maDestructedListeners.end() && (*dest < rpListener)) + ++dest; + + if (dest == maDestructedListeners.end() || *dest != rpListener) + rpListener->Notify(rHint); + } +} + +void SvtBroadcaster::ListenersGone() {} + +SvtBroadcaster::ListenersType& SvtBroadcaster::GetAllListeners() +{ + Normalize(); + return maListeners; +} + +const SvtBroadcaster::ListenersType& SvtBroadcaster::GetAllListeners() const +{ + Normalize(); + return maListeners; +} + +void SvtBroadcaster::PrepareForDestruction() +{ + mbAboutToDie = true; + // the reserve() serves two purpose (1) performance (2) makes sure our iterators do not become invalid + maDestructedListeners.reserve(maListeners.size()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/notify/isethint.cxx b/svl/source/notify/isethint.cxx new file mode 100644 index 000000000..c0012377e --- /dev/null +++ b/svl/source/notify/isethint.cxx @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <svl/isethint.hxx> +#include <svl/itemset.hxx> + +/** + * Copies the SfxItemSet passed as a parameter. + */ +SfxItemSetHint::SfxItemSetHint(const SfxItemSet& rItemSet) + : maItemSet(rItemSet.CloneAsValue()) +{ +} + +SfxItemSetHint::~SfxItemSetHint() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/notify/listener.cxx b/svl/source/notify/listener.cxx new file mode 100644 index 000000000..6b1be0ca2 --- /dev/null +++ b/svl/source/notify/listener.cxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <svl/listener.hxx> +#include <svl/broadcast.hxx> + +SvtListener::~SvtListener() COVERITY_NOEXCEPT_FALSE +{ + // Unregister itself from all broadcasters it's listening to. + EndListeningAll(); +} + +// registers at a specific SvtBroadcaster + +bool SvtListener::StartListening( SvtBroadcaster& rBroadcaster ) +{ + std::pair<BroadcastersType::const_iterator, bool> r = + maBroadcasters.insert(&rBroadcaster); + if (r.second) + { + // This is a new broadcaster. + rBroadcaster.Add(this); + } + return r.second; +} + +void SvtListener::EndListening( SvtBroadcaster& rBroadcaster ) +{ + BroadcastersType::const_iterator it = maBroadcasters.find(&rBroadcaster); + if (it == maBroadcasters.end()) + // Not listening to this broadcaster. + return; + maBroadcasters.erase(it); + + rBroadcaster.Remove(this); +} + +// called from the SvtBroadcaster destructor, used to avoid calling +// back into the broadcaster again +void SvtListener::BroadcasterDying( SvtBroadcaster& rBroadcaster ) +{ + BroadcastersType::const_iterator it = maBroadcasters.find(&rBroadcaster); + if (it != maBroadcasters.end()) + maBroadcasters.erase(it); +} + +void SvtListener::EndListeningAll() +{ + for (SvtBroadcaster* p : maBroadcasters) + { + SvtBroadcaster& rBC = *p; + rBC.Remove(this); + } + maBroadcasters.clear(); +} + + +void SvtListener::CopyAllBroadcasters( const SvtListener& r ) +{ + EndListeningAll(); + BroadcastersType aCopy(r.maBroadcasters); + maBroadcasters.swap(aCopy); + for (SvtBroadcaster* p : maBroadcasters) + { + p->Add(this); + } +} + +void SvtListener::Notify( const SfxHint& /*rHint*/ ) {} + +void SvtListener::Query( QueryBase& /*rQuery*/ ) const {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svl/source/notify/lstner.cxx b/svl/source/notify/lstner.cxx new file mode 100644 index 000000000..40a59960a --- /dev/null +++ b/svl/source/notify/lstner.cxx @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <svl/lstner.hxx> + +#include <svl/SfxBroadcaster.hxx> +#include <sal/backtrace.hxx> +#include <sal/log.hxx> + +#include <algorithm> +#include <cassert> +#include <vector> +#include <memory> + +// copy ctor of class SfxListener + +SfxListener::SfxListener( const SfxListener &rOther ) + : maBCs( rOther.maBCs ) +{ + for ( size_t n = 0; n < maBCs.size(); ++n ) + { + maBCs[n]->AddListener(*this); +#ifdef DBG_UTIL + maCallStacks.emplace( maBCs[n], sal::backtrace_get(10) ); +#endif + } +} + +// unregisters the SfxListener from its SfxBroadcasters + +SfxListener::~SfxListener() COVERITY_NOEXCEPT_FALSE +{ + // unregister at all remaining broadcasters + for ( size_t nPos = 0; nPos < maBCs.size(); ++nPos ) + { + SfxBroadcaster *pBC = maBCs[nPos]; + pBC->RemoveListener(*this); + } +} + + +// unregisters a specific SfxBroadcaster + +void SfxListener::RemoveBroadcaster_Impl( SfxBroadcaster& rBroadcaster ) +{ + auto it = std::find( maBCs.begin(), maBCs.end(), &rBroadcaster ); + if (it != maBCs.end()) { + maBCs.erase( it ); +#ifdef DBG_UTIL + maCallStacks.erase( &rBroadcaster ); +#endif + } +} + + + +/** + Registers a specific SfxBroadcaster. + + Some code uses duplicates as a kind of ref-counting thing i.e. they add and remove listeners + on different code paths, and they only really stop listening when the last EndListening() is called. +*/ +void SfxListener::StartListening(SfxBroadcaster& rBroadcaster, DuplicateHandling eDuplicateHanding) +{ + bool bListeningAlready = IsListening( rBroadcaster ); + +#ifdef DBG_UTIL + if (bListeningAlready && eDuplicateHanding == DuplicateHandling::Unexpected) + { + auto f = maCallStacks.find( &rBroadcaster ); + SAL_WARN("svl", "previous StartListening call came from: " << sal::backtrace_to_string(f->second.get())); + } +#endif + assert(!(bListeningAlready && eDuplicateHanding == DuplicateHandling::Unexpected) && "duplicate listener, try building with DBG_UTIL to find the other insert site."); + + if (!bListeningAlready || eDuplicateHanding != DuplicateHandling::Prevent) + { + rBroadcaster.AddListener(*this); + maBCs.push_back( &rBroadcaster ); +#ifdef DBG_UTIL + maCallStacks.emplace( &rBroadcaster, sal::backtrace_get(10) ); +#endif + assert(IsListening(rBroadcaster) && "StartListening failed"); + } +} + +// unregisters a specific SfxBroadcaster + +void SfxListener::EndListening( SfxBroadcaster& rBroadcaster, bool bRemoveAllDuplicates ) +{ + auto beginIt = maBCs.begin(); + do + { + auto it = std::find( beginIt, maBCs.end(), &rBroadcaster ); + if ( it == maBCs.end() ) + { + break; + } + rBroadcaster.RemoveListener(*this); + beginIt = maBCs.erase( it ); +#ifdef DBG_UTIL + maCallStacks.erase( &rBroadcaster ); +#endif + } + while ( bRemoveAllDuplicates ); +} + + +// unregisters all Broadcasters + +void SfxListener::EndListeningAll() +{ + std::vector<SfxBroadcaster*> aBroadcasters; + std::swap(maBCs, aBroadcasters); + for (SfxBroadcaster *pBC : aBroadcasters) + pBC->RemoveListener(*this); +#ifdef DBG_UTIL + maCallStacks.clear(); +#endif +} + + +bool SfxListener::IsListening( SfxBroadcaster& rBroadcaster ) const +{ + return maBCs.end() != std::find( maBCs.begin(), maBCs.end(), &rBroadcaster ); +} + +sal_uInt16 SfxListener::GetBroadcasterCount() const +{ + return maBCs.size(); +} + +SfxBroadcaster* SfxListener::GetBroadcasterJOE( sal_uInt16 nNo ) const +{ + return maBCs[nNo]; +} + + +// base implementation of notification handler + +void SfxListener::Notify( SfxBroadcaster& rBroadcaster, const SfxHint& ) +{ + (void) rBroadcaster; + assert(IsListening(rBroadcaster)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |