nss: upgrade to release 3.73
[LibreOffice.git] / svl / source / notify / broadcast.cxx
blob6c75e0f464c19893163775892a7a399355112590
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <svl/broadcast.hxx>
21 #include <svl/listener.hxx>
22 #include <svl/hint.hxx>
23 #include <o3tl/safeint.hxx>
24 #include <cassert>
25 #include <algorithm>
27 /**
28 Design Notes
29 -------------------------------
31 This class is extremely heavily used - we can have millions of broadcasters and listeners and we can also
32 have a broadcaster that has a million listeners.
34 So we do a number of things
35 (*) use a cache-dense listener list (std::vector) because caching effects dominate a lot of operations
36 (*) use a sorted list to speed up find operations
37 (*) only sort the list when we absolutely have to, to speed up sequential add/remove operations
38 (*) defer removing items from the list by (ab)using the last bit of the pointer
40 Also we have some complications around destruction because
41 (*) we broadcast a message to indicate that we are destructing
42 (*) which can trigger arbitrality complicated behaviour, including
43 (*) adding a removing things from the in-destruction object!
47 static bool isDeletedPtr(SvtListener* p)
49 /** mark deleted entries by toggling the last bit,which is effectively unused, since the struct we point
50 * to is at least 16-bit aligned. This allows the binary search to continue working even when we have
51 * deleted entries */
52 #if SAL_TYPES_SIZEOFPOINTER == 4
53 return (reinterpret_cast<sal_uInt32>(p) & 0x01) == 0x01;
54 #else
55 return (reinterpret_cast<sal_uInt64>(p) & 0x01) == 0x01;
56 #endif
59 static void markDeletedPtr(SvtListener*& rp)
61 #if SAL_TYPES_SIZEOFPOINTER == 4
62 reinterpret_cast<sal_uInt32&>(rp) |= 0x01;
63 #else
64 reinterpret_cast<sal_uInt64&>(rp) |= 0x01;
65 #endif
68 static void sortListeners(std::vector<SvtListener*>& listeners, size_t firstUnsorted)
70 // Add() only appends new values, so often the container will be sorted expect for one
71 // or few last items. For larger containers it is much more efficient to just handle
72 // the unsorted part.
73 auto sortedEnd = firstUnsorted == 0
74 ? std::is_sorted_until(listeners.begin(),listeners.end())
75 : listeners.begin() + firstUnsorted;
76 if( listeners.end() - sortedEnd == 1 )
77 { // Just one item, insert it in the right place.
78 SvtListener* item = listeners.back();
79 listeners.pop_back();
80 listeners.insert( std::upper_bound( listeners.begin(), listeners.end(), item ), item );
82 else if( o3tl::make_unsigned( sortedEnd - listeners.begin()) > listeners.size() * 3 / 4 )
83 { // Sort the unsorted part and then merge.
84 std::sort( sortedEnd, listeners.end());
85 std::inplace_merge( listeners.begin(), sortedEnd, listeners.end());
87 else
89 std::sort(listeners.begin(), listeners.end());
93 void SvtBroadcaster::Normalize() const
95 // clear empty slots first, because then we often have to do very little sorting
96 if (mnEmptySlots)
98 maListeners.erase(
99 std::remove_if(maListeners.begin(), maListeners.end(), [] (SvtListener* p) { return isDeletedPtr(p); }),
100 maListeners.end());
101 mnEmptySlots = 0;
104 if (mnListenersFirstUnsorted != static_cast<sal_Int32>(maListeners.size()))
106 sortListeners(maListeners, mnListenersFirstUnsorted);
107 mnListenersFirstUnsorted = maListeners.size();
110 if (!mbDestNormalized)
112 sortListeners(maDestructedListeners, 0);
113 mbDestNormalized = true;
117 void SvtBroadcaster::Add( SvtListener* p )
119 assert(!mbDisposing && "called inside my own destructor?");
120 assert(!mbAboutToDie && "called after PrepareForDestruction()?");
121 if (mbDisposing || mbAboutToDie)
122 return;
123 // Avoid normalizing if the item appended keeps the container sorted.
124 auto nRealSize = static_cast<sal_Int32>(maListeners.size() - mnEmptySlots);
125 auto bSorted = mnListenersFirstUnsorted == nRealSize;
126 if (maListeners.empty() || (bSorted && maListeners.back() <= p))
128 ++mnListenersFirstUnsorted;
129 maListeners.push_back(p);
130 return;
132 // see if we can stuff it into an empty slot, which succeeds surprisingly often in
133 // some calc workloads where it removes and then re-adds the same listener
134 if (mnEmptySlots && bSorted)
136 auto it = std::lower_bound(maListeners.begin(), maListeners.end(), p);
137 if (it != maListeners.end() && isDeletedPtr(*it))
139 *it = p;
140 ++mnListenersFirstUnsorted;
141 --mnEmptySlots;
142 return;
145 maListeners.push_back(p);
148 void SvtBroadcaster::Remove( SvtListener* p )
150 if (mbDisposing)
151 return;
153 if (mbAboutToDie)
155 // only reset mbDestNormalized if we are going to become unsorted
156 if (!maDestructedListeners.empty() && maDestructedListeners.back() > p)
157 mbDestNormalized = false;
158 maDestructedListeners.push_back(p);
159 return;
162 // We only need to fully normalize if one or more Add()s have been performed that make the array unsorted.
163 auto nRealSize = static_cast<sal_Int32>(maListeners.size() - mnEmptySlots);
164 if (mnListenersFirstUnsorted != nRealSize || (maListeners.size() > 1024 && maListeners.size() / nRealSize > 16))
165 Normalize();
167 auto it = std::lower_bound(maListeners.begin(), maListeners.end(), p);
168 assert (it != maListeners.end() && *it == p);
169 if (it != maListeners.end() && *it == p)
171 markDeletedPtr(*it);
172 ++mnEmptySlots;
173 --mnListenersFirstUnsorted;
176 if (maListeners.size() - mnEmptySlots == 0)
177 ListenersGone();
180 SvtBroadcaster::SvtBroadcaster()
181 : mnEmptySlots(0)
182 , mnListenersFirstUnsorted(0)
183 , mbAboutToDie(false)
184 , mbDisposing(false)
185 , mbDestNormalized(true)
188 SvtBroadcaster::SvtBroadcaster( const SvtBroadcaster &rBC ) :
189 mnEmptySlots(0), mnListenersFirstUnsorted(0),
190 mbAboutToDie(false), mbDisposing(false),
191 mbDestNormalized(true)
193 assert(!rBC.mbAboutToDie && "copying an object marked with PrepareForDestruction()?");
194 assert(!rBC.mbDisposing && "copying an object that is in its destructor?");
196 rBC.Normalize(); // so that insert into ourself is in-order, and therefore we do not need to Normalize()
197 maListeners.reserve(rBC.maListeners.size());
198 for (SvtListener* p : rBC.maListeners)
199 p->StartListening(*this); // this will call back into this->Add()
202 SvtBroadcaster::~SvtBroadcaster()
204 mbDisposing = true;
205 Broadcast( SfxHint(SfxHintId::Dying) );
207 Normalize();
209 // now when both lists are sorted, we can linearly unregister all
210 // listeners, with the exception of those that already asked to be removed
211 // during their own destruction
212 ListenersType::const_iterator dest(maDestructedListeners.begin());
213 for (auto& rpListener : maListeners)
215 // skip the destructed ones
216 while (dest != maDestructedListeners.end() && (*dest < rpListener))
217 ++dest;
219 if (dest == maDestructedListeners.end() || *dest != rpListener)
220 rpListener->BroadcasterDying(*this);
224 void SvtBroadcaster::Broadcast( const SfxHint &rHint )
226 Normalize();
228 ListenersType::const_iterator dest(maDestructedListeners.begin());
229 ListenersType aListeners(maListeners); // this copy is important to avoid erasing entries while iterating
230 for (auto& rpListener : aListeners)
232 // skip the destructed ones
233 while (dest != maDestructedListeners.end() && (*dest < rpListener))
234 ++dest;
236 if (dest == maDestructedListeners.end() || *dest != rpListener)
237 rpListener->Notify(rHint);
241 void SvtBroadcaster::ListenersGone() {}
243 SvtBroadcaster::ListenersType& SvtBroadcaster::GetAllListeners()
245 Normalize();
246 return maListeners;
249 const SvtBroadcaster::ListenersType& SvtBroadcaster::GetAllListeners() const
251 Normalize();
252 return maListeners;
255 bool SvtBroadcaster::HasListeners() const
257 return (maListeners.size() - mnEmptySlots) > 0;
260 void SvtBroadcaster::PrepareForDestruction()
262 mbAboutToDie = true;
263 // the reserve() serves two purpose (1) performance (2) makes sure our iterators do not become invalid
264 maDestructedListeners.reserve(maListeners.size());
267 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */