/src/libreoffice/svl/source/notify/broadcast.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
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/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
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 . |
18 | | */ |
19 | | |
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 <cstdint> |
26 | | #include <algorithm> |
27 | | |
28 | | /** |
29 | | Design Notes |
30 | | ------------------------------- |
31 | | |
32 | | This class is extremely heavily used - we can have millions of broadcasters and listeners and we can also |
33 | | have a broadcaster that has a million listeners. |
34 | | |
35 | | So we do a number of things |
36 | | (*) use a cache-dense listener list (std::vector) because caching effects dominate a lot of operations |
37 | | (*) use a sorted list to speed up find operations |
38 | | (*) only sort the list when we absolutely have to, to speed up sequential add/remove operations |
39 | | (*) defer removing items from the list by (ab)using the last bit of the pointer |
40 | | |
41 | | Also we have some complications around destruction because |
42 | | (*) we broadcast a message to indicate that we are destructing |
43 | | (*) which can trigger arbitrality complicated behaviour, including |
44 | | (*) adding a removing things from the in-destruction object! |
45 | | |
46 | | */ |
47 | | |
48 | | static bool isDeletedPtr(SvtListener* p) |
49 | 154M | { |
50 | | /** mark deleted entries by toggling the last bit,which is effectively unused, since the struct we point |
51 | | * to is at least 16-bit aligned. This allows the binary search to continue working even when we have |
52 | | * deleted entries */ |
53 | 154M | return (reinterpret_cast<uintptr_t>(p) & 0x01) == 0x01; |
54 | 154M | } |
55 | | |
56 | | static void markDeletedPtr(SvtListener*& rp) |
57 | 5.74M | { |
58 | 5.74M | reinterpret_cast<uintptr_t&>(rp) |= 0x01; |
59 | 5.74M | } |
60 | | |
61 | | static void sortListeners(std::vector<SvtListener*>& listeners, size_t firstUnsorted) |
62 | 973k | { |
63 | | // Add() only appends new values, so often the container will be sorted expect for one |
64 | | // or few last items. For larger containers it is much more efficient to just handle |
65 | | // the unsorted part. |
66 | 973k | auto sortedEnd = firstUnsorted == 0 |
67 | 973k | ? std::is_sorted_until(listeners.begin(),listeners.end()) |
68 | 973k | : listeners.begin() + firstUnsorted; |
69 | 973k | if( listeners.end() - sortedEnd == 1 ) |
70 | 450k | { // Just one item, insert it in the right place. |
71 | 450k | SvtListener* item = listeners.back(); |
72 | 450k | listeners.pop_back(); |
73 | 450k | listeners.insert( std::upper_bound( listeners.begin(), listeners.end(), item ), item ); |
74 | 450k | } |
75 | 523k | else if( o3tl::make_unsigned( sortedEnd - listeners.begin()) > listeners.size() * 3 / 4 ) |
76 | 324k | { // Sort the unsorted part and then merge. |
77 | 324k | std::sort( sortedEnd, listeners.end()); |
78 | 324k | std::inplace_merge( listeners.begin(), sortedEnd, listeners.end()); |
79 | 324k | } |
80 | 198k | else |
81 | 198k | { |
82 | 198k | std::sort(listeners.begin(), listeners.end()); |
83 | 198k | } |
84 | 973k | } |
85 | | |
86 | | void SvtBroadcaster::Normalize() const |
87 | 172M | { |
88 | | // clear empty slots first, because then we often have to do very little sorting |
89 | 172M | if (mnEmptySlots) |
90 | 1.30M | { |
91 | 151M | std::erase_if(maListeners, [] (SvtListener* p) { return isDeletedPtr(p); }); |
92 | 1.30M | mnEmptySlots = 0; |
93 | 1.30M | } |
94 | | |
95 | 172M | if (mnListenersFirstUnsorted != static_cast<sal_Int32>(maListeners.size())) |
96 | 970k | { |
97 | 970k | sortListeners(maListeners, mnListenersFirstUnsorted); |
98 | 970k | mnListenersFirstUnsorted = maListeners.size(); |
99 | 970k | } |
100 | | |
101 | 172M | if (!mbDestNormalized) |
102 | 3.48k | { |
103 | 3.48k | sortListeners(maDestructedListeners, 0); |
104 | 3.48k | mbDestNormalized = true; |
105 | 3.48k | } |
106 | 172M | } |
107 | | |
108 | | void SvtBroadcaster::Add( SvtListener* p ) |
109 | 9.89M | { |
110 | 9.89M | assert(!mbDisposing && "called inside my own destructor?"); |
111 | 9.89M | assert(!mbAboutToDie && "called after PrepareForDestruction()?"); |
112 | 9.89M | if (mbDisposing || mbAboutToDie) |
113 | 0 | return; |
114 | | // Avoid normalizing if the item appended keeps the container sorted. |
115 | 9.89M | auto nRealSize = static_cast<sal_Int32>(maListeners.size() - mnEmptySlots); |
116 | 9.89M | auto bSorted = mnListenersFirstUnsorted == nRealSize; |
117 | 9.89M | if (maListeners.empty() || (bSorted && maListeners.back() <= p)) |
118 | 2.31M | { |
119 | 2.31M | ++mnListenersFirstUnsorted; |
120 | 2.31M | maListeners.push_back(p); |
121 | 2.31M | return; |
122 | 2.31M | } |
123 | | // see if we can stuff it into an empty slot, which succeeds surprisingly often in |
124 | | // some calc workloads where it removes and then re-adds the same listener |
125 | 7.58M | if (mnEmptySlots && bSorted) |
126 | 2.51M | { |
127 | 2.51M | auto it = std::lower_bound(maListeners.begin(), maListeners.end(), p); |
128 | 2.51M | if (it != maListeners.end() && isDeletedPtr(*it)) |
129 | 1.93M | { |
130 | 1.93M | *it = p; |
131 | 1.93M | ++mnListenersFirstUnsorted; |
132 | 1.93M | --mnEmptySlots; |
133 | 1.93M | return; |
134 | 1.93M | } |
135 | 2.51M | } |
136 | 5.64M | maListeners.push_back(p); |
137 | 5.64M | } |
138 | | |
139 | | void SvtBroadcaster::Remove( SvtListener* p ) |
140 | 7.59M | { |
141 | 7.59M | if (mbDisposing) |
142 | 886k | return; |
143 | | |
144 | 6.70M | if (mbAboutToDie) |
145 | 962k | { |
146 | | // only reset mbDestNormalized if we are going to become unsorted |
147 | 962k | if (!maDestructedListeners.empty() && maDestructedListeners.back() > p) |
148 | 27.1k | mbDestNormalized = false; |
149 | 962k | maDestructedListeners.push_back(p); |
150 | 962k | return; |
151 | 962k | } |
152 | | |
153 | | // We only need to fully normalize if one or more Add()s have been performed that make the array unsorted. |
154 | 5.74M | auto nRealSize = static_cast<sal_Int32>(maListeners.size() - mnEmptySlots); |
155 | 5.74M | if (mnListenersFirstUnsorted != nRealSize || (maListeners.size() > 1024 && maListeners.size() / nRealSize > 16)) |
156 | 830k | Normalize(); |
157 | | |
158 | 5.74M | auto it = std::lower_bound(maListeners.begin(), maListeners.end(), p); |
159 | 5.74M | assert (it != maListeners.end() && *it == p); |
160 | 5.74M | if (it != maListeners.end() && *it == p) |
161 | 5.74M | { |
162 | 5.74M | markDeletedPtr(*it); |
163 | 5.74M | ++mnEmptySlots; |
164 | 5.74M | --mnListenersFirstUnsorted; |
165 | 5.74M | } |
166 | | |
167 | 5.74M | if (!HasListeners()) |
168 | 1.68M | ListenersGone(); |
169 | 5.74M | } |
170 | | |
171 | | SvtBroadcaster::SvtBroadcaster( const SvtBroadcaster &rBC ) : |
172 | 0 | mnEmptySlots(0), mnListenersFirstUnsorted(0), |
173 | 0 | mbAboutToDie(false), mbDisposing(false), |
174 | 0 | mbDestNormalized(true) |
175 | 0 | { |
176 | 0 | assert(!rBC.mbAboutToDie && "copying an object marked with PrepareForDestruction()?"); |
177 | 0 | assert(!rBC.mbDisposing && "copying an object that is in its destructor?"); |
178 | |
|
179 | 0 | rBC.Normalize(); // so that insert into ourself is in-order, and therefore we do not need to Normalize() |
180 | 0 | maListeners.reserve(rBC.maListeners.size()); |
181 | 0 | for (SvtListener* p : rBC.maListeners) |
182 | 0 | p->StartListening(*this); // this will call back into this->Add() |
183 | 0 | } |
184 | | |
185 | | SvtBroadcaster::~SvtBroadcaster() |
186 | 58.4M | { |
187 | 58.4M | mbDisposing = true; |
188 | 58.4M | Broadcast( SfxHint(SfxHintId::Dying) ); |
189 | | |
190 | 58.4M | Normalize(); |
191 | | |
192 | | // now when both lists are sorted, we can linearly unregister all |
193 | | // listeners, with the exception of those that already asked to be removed |
194 | | // during their own destruction |
195 | 58.4M | ListenersType::const_iterator dest(maDestructedListeners.begin()); |
196 | 58.4M | for (auto& rpListener : maListeners) |
197 | 4.15M | { |
198 | | // skip the destructed ones |
199 | 5.10M | while (dest != maDestructedListeners.end() && (*dest < rpListener)) |
200 | 954k | ++dest; |
201 | | |
202 | 4.15M | if (dest == maDestructedListeners.end() || *dest != rpListener) |
203 | 3.18M | rpListener->BroadcasterDying(*this); |
204 | 4.15M | } |
205 | 58.4M | } |
206 | | |
207 | | void SvtBroadcaster::Broadcast( const SfxHint &rHint ) |
208 | 112M | { |
209 | 112M | Normalize(); |
210 | | |
211 | 112M | ListenersType::const_iterator dest(maDestructedListeners.begin()); |
212 | 112M | ListenersType aListeners(maListeners); // this copy is important to avoid erasing entries while iterating |
213 | 112M | for (auto& rpListener : aListeners) |
214 | 6.00M | { |
215 | | // skip the destructed ones |
216 | 6.96M | while (dest != maDestructedListeners.end() && (*dest < rpListener)) |
217 | 954k | ++dest; |
218 | | |
219 | 6.00M | if (dest == maDestructedListeners.end() || *dest != rpListener) |
220 | 5.04M | rpListener->Notify(rHint); |
221 | 6.00M | } |
222 | 112M | } |
223 | | |
224 | 1.68M | void SvtBroadcaster::ListenersGone() {} |
225 | | |
226 | | SvtBroadcaster::ListenersType& SvtBroadcaster::GetAllListeners() |
227 | 0 | { |
228 | 0 | Normalize(); |
229 | 0 | return maListeners; |
230 | 0 | } |
231 | | |
232 | | const SvtBroadcaster::ListenersType& SvtBroadcaster::GetAllListeners() const |
233 | 0 | { |
234 | 0 | Normalize(); |
235 | 0 | return maListeners; |
236 | 0 | } |
237 | | |
238 | | void SvtBroadcaster::PrepareForDestruction() |
239 | 496k | { |
240 | 496k | mbAboutToDie = true; |
241 | | // the reserve() serves two purpose (1) performance (2) makes sure our iterators do not become invalid |
242 | 496k | maDestructedListeners.reserve(maListeners.size()); |
243 | 496k | } |
244 | | |
245 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |