/src/libreoffice/svl/source/items/globalpool.cxx
Line | Count | Source (jump to first uncovered line) |
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/itemset.hxx> |
21 | | #include <svl/itempool.hxx> |
22 | | #include <svl/setitem.hxx> |
23 | | #include <sal/log.hxx> |
24 | | |
25 | | static bool g_bDisableItemInstanceManager(getenv("SVL_DISABLE_ITEM_INSTANCE_MANAGER")); |
26 | | static bool g_bShareImmediately(getenv("SVL_SHARE_ITEMS_GLOBALLY_INSTANTLY")); |
27 | 32.3M | #define NUMBER_OF_UNSHARED_INSTANCES (50) |
28 | | |
29 | | #ifdef DBG_UTIL |
30 | | |
31 | | // <WhichID, <number of entries, typeid_name>> |
32 | | typedef std::unordered_map<sal_uInt16, std::pair<sal_uInt32, const char*>> HightestUsage; |
33 | | static HightestUsage aHightestUsage; |
34 | | |
35 | | static void addUsage(const SfxPoolItem& rCandidate) |
36 | | { |
37 | | HightestUsage::iterator aHit(aHightestUsage.find(rCandidate.Which())); |
38 | | if (aHit == aHightestUsage.end()) |
39 | | { |
40 | | aHightestUsage.insert({ rCandidate.Which(), { 1, typeid(rCandidate).name() } }); |
41 | | return; |
42 | | } |
43 | | aHit->second.first++; |
44 | | } |
45 | | |
46 | | void listSfxPoolItemsWithHighestUsage(sal_uInt16 nNum) |
47 | | { |
48 | | struct sorted |
49 | | { |
50 | | sal_uInt16 nWhich; |
51 | | sal_uInt32 nUsage; |
52 | | const char* pType; |
53 | | sorted(sal_uInt16 _nWhich, sal_uInt32 _nUsage, const char* _pType) |
54 | | : nWhich(_nWhich) |
55 | | , nUsage(_nUsage) |
56 | | , pType(_pType) |
57 | | { |
58 | | } |
59 | | bool operator<(const sorted& rDesc) const { return nUsage > rDesc.nUsage; } |
60 | | }; |
61 | | std::vector<sorted> aSorted; |
62 | | aSorted.reserve(aHightestUsage.size()); |
63 | | for (const auto& rEntry : aHightestUsage) |
64 | | aSorted.emplace_back(rEntry.first, rEntry.second.first, rEntry.second.second); |
65 | | std::sort(aSorted.begin(), aSorted.end()); |
66 | | sal_uInt16 a(0); |
67 | | SAL_INFO("svl.items", |
68 | | "ITEM: List of the " << nNum << " SfxPoolItems with highest non-RefCounted usages:"); |
69 | | for (const auto& rEntry : aSorted) |
70 | | { |
71 | | SAL_INFO("svl.items", " ITEM(" << a << "): Which: " << rEntry.nWhich |
72 | | << " Uses: " << rEntry.nUsage << " Type: " << rEntry.pType); |
73 | | if (++a >= nNum) |
74 | | break; |
75 | | } |
76 | | } |
77 | | |
78 | | #endif |
79 | | |
80 | | void DefaultItemInstanceManager::add(const SfxPoolItem& rItem) |
81 | 5.73M | { |
82 | 5.73M | maRegistered[rItem.Which()].insert(&rItem); |
83 | 5.73M | } |
84 | | |
85 | | void DefaultItemInstanceManager::remove(const SfxPoolItem& rItem) |
86 | 5.74M | { |
87 | 5.74M | maRegistered[rItem.Which()].erase(&rItem); |
88 | 5.74M | } |
89 | | |
90 | | // Class that implements global Item sharing. It uses rtti to |
91 | | // associate every Item-derivation with a possible incarnation |
92 | | // of a DefaultItemInstanceManager. This is the default, it will |
93 | | // give direct implementations at the Items that overload |
94 | | // getItemInstanceManager() preference. These are expected to |
95 | | // return static instances of a derived implementation of a |
96 | | // ItemInstanceManager. |
97 | | // All in all there are now the following possibilities to support |
98 | | // this for individual Item derivations: |
99 | | // (1) Do nothing: |
100 | | // In that case, if the Item is shareable, the new mechanism |
101 | | // will kick in: It will start sharing the Item globally, |
102 | | // but not immediately: After a defined amount of allowed |
103 | | // non-shared occurrences (look for NUMBER_OF_UNSHARED_INSTANCES) |
104 | | // an instance of the default ItemInstanceManager, a |
105 | | // DefaultItemInstanceManager, will be incarnated and used. |
106 | | // NOTE: Mixing shared/unshared instances is not a problem (we |
107 | | // might even implement a kind of 're-hash' when this kicks in, |
108 | | // but is not really needed). |
109 | | // (2) Overload getItemInstanceManager for SfxPoolItem in a class |
110 | | // derived from SfxPoolItem and... |
111 | | // (2a) Return a static incarnation of DefaultItemInstanceManager to |
112 | | // immediately start global sharing of that Item derivation. |
113 | | // (2b) Implement and return your own implementation and static |
114 | | // incarnation of ItemInstanceManager to do something better/ |
115 | | // faster that the default implementation can do. Example: |
116 | | // SvxFontItem, uses hashing now. |
117 | | // There are two supported ENVVARs to use: |
118 | | // (a) SVL_DISABLE_ITEM_INSTANCE_MANAGER: |
119 | | // This disables the mechanism of global Item sharing completely. |
120 | | // This can be used to test/check speed/memory needs compared with |
121 | | // using it, but also may come in handy to check if evtl. errors/ |
122 | | // regressions have to do with it. |
123 | | // (b) SVL_SHARE_ITEMS_GLOBALLY_INSTANTLY: |
124 | | // This internally forces the NUMBER_OF_UNSHARED_INSTANCES to be |
125 | | // ignored and start sharing ALL Item derivations instantly. |
126 | | class InstanceManagerHelper |
127 | | { |
128 | | typedef std::unordered_map<SfxItemType, std::pair<sal_uInt16, ItemInstanceManager*>> |
129 | | managerTypeMap; |
130 | | managerTypeMap maManagerPerType; |
131 | | |
132 | | public: |
133 | 108 | InstanceManagerHelper() {} |
134 | | ~InstanceManagerHelper() |
135 | 0 | { |
136 | 0 | for (auto& rCandidate : maManagerPerType) |
137 | 0 | if (nullptr != rCandidate.second.second) |
138 | 0 | delete rCandidate.second.second; |
139 | 0 | } |
140 | | |
141 | | ItemInstanceManager* getOrCreateItemInstanceManager(const SfxPoolItem& rItem) |
142 | 167M | { |
143 | | // deactivated? |
144 | 167M | if (g_bDisableItemInstanceManager) |
145 | 0 | return nullptr; |
146 | | |
147 | | // Item cannot be shared? |
148 | 167M | if (!rItem.isShareable()) |
149 | 12.1M | return nullptr; |
150 | | |
151 | | // Prefer getting an ItemInstanceManager directly from |
152 | | // the Item: These are the extra implemented (and thus |
153 | | // hopefully fastest) incarnations |
154 | 154M | ItemInstanceManager* pManager(rItem.getItemInstanceManager()); |
155 | | |
156 | | // Check for correct SfxItemType, there may be derivations of that class. |
157 | | // Note that Managers from the Items are *not* added to local list, |
158 | | // they are expected to be static instances at the Items for fastest access |
159 | 154M | if (nullptr != pManager && pManager->ItemType() == rItem.ItemType()) |
160 | 57.5M | return pManager; |
161 | | |
162 | | // check local memory for existing entry |
163 | 97.3M | managerTypeMap::iterator aHit(maManagerPerType.find(rItem.ItemType())); |
164 | | |
165 | | // no instance yet |
166 | 97.3M | if (aHit == maManagerPerType.end()) |
167 | 1.21k | { |
168 | | // create a default one to start usage-counting |
169 | 1.21k | if (g_bShareImmediately) |
170 | 0 | { |
171 | | // create, insert locally and immediately start sharing |
172 | 0 | ItemInstanceManager* pNew; |
173 | 0 | if (rItem.supportsHashCode()) |
174 | 0 | pNew = new HashedItemInstanceManager(rItem.ItemType()); |
175 | 0 | else |
176 | 0 | pNew = new DefaultItemInstanceManager(rItem.ItemType()); |
177 | 0 | maManagerPerType.insert({ rItem.ItemType(), std::make_pair(0, pNew) }); |
178 | 0 | return pNew; |
179 | 0 | } |
180 | | |
181 | | // start countdown from NUMBER_OF_UNSHARED_INSTANCES until zero is reached |
182 | 1.21k | maManagerPerType.insert( |
183 | 1.21k | { rItem.ItemType(), std::make_pair(NUMBER_OF_UNSHARED_INSTANCES, nullptr) }); |
184 | 1.21k | return nullptr; |
185 | 1.21k | } |
186 | | |
187 | | // if there is already an ItemInstanceManager incarnated, return it |
188 | 97.2M | if (nullptr != aHit->second.second) |
189 | 64.9M | return aHit->second.second; |
190 | | |
191 | 32.3M | if (aHit->second.first > 0) |
192 | 32.3M | { |
193 | | // still not the needed number of hits, countdown & return nullptr |
194 | 32.3M | aHit->second.first--; |
195 | 32.3M | return nullptr; |
196 | 32.3M | } |
197 | | |
198 | | // here the countdown is zero and there is not yet a ItemInstanceManager |
199 | | // incarnated. Do so, register and return it |
200 | 398 | assert(nullptr == aHit->second.second); |
201 | 398 | ItemInstanceManager* pNew; |
202 | 398 | if (rItem.supportsHashCode()) |
203 | 237 | pNew = new HashedItemInstanceManager(rItem.ItemType()); |
204 | 161 | else |
205 | 161 | pNew = new DefaultItemInstanceManager(rItem.ItemType()); |
206 | 398 | aHit->second.second = pNew; |
207 | | |
208 | 398 | return pNew; |
209 | 32.3M | } |
210 | | |
211 | | ItemInstanceManager* getExistingItemInstanceManager(const SfxPoolItem& rItem) |
212 | 61.6M | { |
213 | | // deactivated? |
214 | 61.6M | if (g_bDisableItemInstanceManager) |
215 | 0 | return nullptr; |
216 | | |
217 | | // Item cannot be shared? |
218 | 61.6M | if (!rItem.isShareable()) |
219 | 8.38M | return nullptr; |
220 | | |
221 | | // Prefer getting an ItemInstanceManager directly from |
222 | | // the Item: These are the extra implemented (and thus |
223 | | // hopefully fastest) incarnations |
224 | 53.2M | ItemInstanceManager* pManager(rItem.getItemInstanceManager()); |
225 | | |
226 | | // Check for correct SfxItemType, there may be derivations of that class. |
227 | | // Note that Managers from the Items are *not* added to local list, |
228 | | // they are expected to be static instances at the Items |
229 | 53.2M | if (nullptr != pManager && pManager->ItemType() == rItem.ItemType()) |
230 | 10.8M | return pManager; |
231 | | |
232 | | // check local memory for existing entry |
233 | 42.3M | managerTypeMap::iterator aHit(maManagerPerType.find(rItem.ItemType())); |
234 | | |
235 | 42.3M | if (aHit == maManagerPerType.end()) |
236 | | // no instance yet, return nullptr |
237 | 3.73M | return nullptr; |
238 | | |
239 | | // if there is already a ItemInstanceManager incarnated, return it |
240 | 38.6M | if (nullptr != aHit->second.second) |
241 | 6.27M | return aHit->second.second; |
242 | | |
243 | | // count-up needed number of hits again if item is released |
244 | 32.3M | if (aHit->second.first < NUMBER_OF_UNSHARED_INSTANCES) |
245 | 32.3M | aHit->second.first++; |
246 | | |
247 | 32.3M | return nullptr; |
248 | 38.6M | } |
249 | | }; |
250 | | |
251 | | // the single static instance that takes over that global Item sharing |
252 | | static InstanceManagerHelper aInstanceManagerHelper; |
253 | | |
254 | | SfxPoolItem const* implCreateItemEntry(const SfxItemPool& rPool, SfxPoolItem const* pSource, |
255 | | bool bPassingOwnership) |
256 | 1.23G | { |
257 | 1.23G | if (nullptr == pSource) |
258 | | // SfxItemState::UNKNOWN aka current default (nullptr) |
259 | | // just use/return nullptr |
260 | 0 | return nullptr; |
261 | | |
262 | 1.23G | if (pSource->isStaticDefault()) |
263 | | // static default Items can just be used without RefCounting |
264 | | // NOTE: This now includes IsInvalidItem/IsDisabledItem |
265 | 103M | return pSource; |
266 | | |
267 | 1.12G | if (0 == pSource->Which()) |
268 | 0 | { |
269 | | // There should be no Items with 0 == WhichID, but there are some |
270 | | // constructed for dialog return values AKA result (look for SetReturnValue) |
271 | | // these need to be cloned (currently...) |
272 | 0 | if (bPassingOwnership) |
273 | 0 | return pSource; |
274 | 0 | return pSource->Clone(); |
275 | 0 | } |
276 | | |
277 | 1.12G | if (pSource->isDynamicDefault() && rPool.GetPoolDefaultItem(pSource->Which()) == pSource) |
278 | | // dynamic defaults are not allowed to 'leave' the Pool they are |
279 | | // defined for. We can check by comparing the PoolDefault (the |
280 | | // PoolDefaultItem) to pSource by ptr compare (instance). When |
281 | | // same Item we can use without RefCount. Else it will be cloned |
282 | | // below the standard way. |
283 | 3.31M | return pSource; |
284 | | |
285 | | #ifdef DBG_UTIL |
286 | | // remember WhichID due to being able to assert Clone() error(s) |
287 | | const sal_uInt16 nWhich(pSource->Which()); |
288 | | #endif |
289 | | |
290 | 1.12G | if (SfxItemPool::IsSlot(pSource->Which())) |
291 | 15.4M | { |
292 | | // SlotItems were always cloned in original (even when bPassingOwnership), |
293 | | // so do that here, too (but without bPassingOwnership). |
294 | | // They do not need to be registered at pool (actually impossible, pools |
295 | | // do not have entries for SlotItems) so handle here early |
296 | 15.4M | if (!bPassingOwnership) |
297 | 15.3M | { |
298 | 15.3M | pSource = pSource->Clone(rPool.GetMasterPool()); |
299 | | // ARGH! Found out that *some* ::Clone implementations fail to also clone the |
300 | | // WhichID set at the original Item, e.g. SfxFrameItem. Assert, this is an error |
301 | | #ifdef DBG_UTIL |
302 | | assert(pSource->Which() == nWhich |
303 | | && "ITEM: Clone of Item did NOT copy/set WhichID (!)"); |
304 | | #endif |
305 | 15.3M | } |
306 | | |
307 | 15.4M | return pSource; |
308 | 15.4M | } |
309 | | |
310 | | // get the pool with which ItemSets have to work, plus get the |
311 | | // pool at which the WhichID is defined, so calls to it do not |
312 | | // have to do this repeatedly |
313 | 1.11G | SfxItemPool* pMasterPool(rPool.GetMasterPool()); |
314 | 1.11G | assert(nullptr != pMasterPool); |
315 | | |
316 | | // The Item itself is shareable when it is used/added at an instance |
317 | | // that RefCounts the Item, SfxItemPool or SfxPoolItemHolder. Try |
318 | | // to share items that are already shared |
319 | 1.11G | if (pSource->GetRefCount() > 0) |
320 | 949M | { |
321 | 949M | if (pSource->isShareable()) |
322 | 943M | { |
323 | | // SfxSetItems cannot be shared if they are in/use another pool |
324 | 943M | if (!pSource->isSetItem() |
325 | 943M | || static_cast<const SfxSetItem*>(pSource)->GetItemSet().GetPool() == pMasterPool) |
326 | 943M | { |
327 | | // If we get here we can share the Item |
328 | 943M | pSource->AddRef(); |
329 | 943M | return pSource; |
330 | 943M | } |
331 | 943M | } |
332 | 949M | } |
333 | | |
334 | | // try to get an ItemInstanceManager for global Item instance sharing |
335 | 167M | ItemInstanceManager* pManager(aInstanceManagerHelper.getOrCreateItemInstanceManager(*pSource)); |
336 | | |
337 | | // check if we can globally share the Item using an ItemInstanceManager |
338 | 167M | if (pManager) |
339 | 122M | { |
340 | 122M | const SfxPoolItem* pAlternative(pManager->find(*pSource)); |
341 | 122M | if (pAlternative) |
342 | 105M | { |
343 | | // Here we do *not* need to check if it is an SfxSetItem |
344 | | // and cannot be shared if they are in/use another pool: |
345 | | // The SfxItemSet::operator== will check for SfxItemPools |
346 | | // being equal, thus when found in global share the Pool |
347 | | // cannot be equal |
348 | | |
349 | | // need to delete evtl. handed over ownership change Item |
350 | 105M | if (bPassingOwnership) |
351 | 13.7M | delete pSource; |
352 | | |
353 | | // If we get here we can share the Item |
354 | 105M | pAlternative->AddRef(); |
355 | 105M | return pAlternative; |
356 | 105M | } |
357 | 122M | } |
358 | | |
359 | | // check if the handed over and to be directly used item is a |
360 | | // SfxSetItem, that would make it pool-dependent. It then must have |
361 | | // the same target-pool, ensure that by the cost of cloning it |
362 | | // (should not happen) |
363 | 61.6M | if (bPassingOwnership && pSource->isSetItem() |
364 | 61.6M | && static_cast<const SfxSetItem*>(pSource)->GetItemSet().GetPool() != pMasterPool) |
365 | 0 | { |
366 | 0 | const SfxPoolItem* pOld(pSource); |
367 | 0 | pSource = pSource->Clone(pMasterPool); |
368 | | #ifdef DBG_UTIL |
369 | | assert(pSource->Which() == nWhich && "ITEM: Clone of Item did NOT copy/set WhichID (!)"); |
370 | | #endif |
371 | 0 | delete pOld; |
372 | 0 | } |
373 | | |
374 | | #ifdef DBG_UTIL |
375 | | // create statistics for listSfxPoolItemsWithHighestUsage |
376 | | addUsage(*pSource); |
377 | | #endif |
378 | | |
379 | | // when we reach this line we know that we have to add/create a new item. If |
380 | | // bPassingOwnership is given just use the item, else clone it |
381 | 61.6M | if (!bPassingOwnership) |
382 | 57.0M | { |
383 | 57.0M | auto pPreviousSource = pSource; |
384 | 57.0M | pSource = pSource->Clone(pMasterPool); |
385 | | #ifdef DBG_UTIL |
386 | | assert(pSource->Which() == nWhich && "ITEM: Clone of Item did NOT copy/set WhichID (!)"); |
387 | | #endif |
388 | 57.0M | SAL_WARN_IF(typeid(*pPreviousSource) != typeid(*pSource), "svl", |
389 | 57.0M | "wrong item from Clone(), expected " << typeid(*pPreviousSource).name() |
390 | 57.0M | << " but got " << typeid(*pSource).name()); |
391 | 57.0M | assert(typeid(*pPreviousSource) == typeid(*pSource) && "wrong item from Clone()"); |
392 | 57.0M | } |
393 | | |
394 | | // increase RefCnt 0->1 |
395 | 61.6M | pSource->AddRef(); |
396 | | |
397 | | // check if we should register this Item for the global |
398 | | // ItemInstanceManager mechanism (only for shareable Items) |
399 | 61.6M | if (nullptr != pManager) |
400 | 17.1M | pManager->add(*pSource); |
401 | | |
402 | 61.6M | return pSource; |
403 | 61.6M | } |
404 | | |
405 | | void implCleanupItemEntry(const SfxPoolItem* pSource) |
406 | 1.91G | { |
407 | 1.91G | if (nullptr == pSource) |
408 | | // no entry, done |
409 | 683M | return; |
410 | | |
411 | 1.23G | if (pSource->isStaticDefault()) |
412 | | // static default Items can just be used without RefCounting |
413 | | // NOTE: This now includes IsInvalidItem/IsDisabledItem |
414 | 103M | return; |
415 | | |
416 | 1.12G | if (0 == pSource->Which()) |
417 | 0 | { |
418 | | // There should be no Items with 0 == WhichID, but there are some |
419 | | // constructed for dialog return values AKA result (look for SetReturnValue) |
420 | | // and need to be deleted |
421 | 0 | delete pSource; |
422 | 0 | return; |
423 | 0 | } |
424 | | |
425 | 1.12G | if (pSource->isDynamicDefault()) |
426 | | // dynamic default Items can only be used without RefCounting |
427 | | // when same pool. this is already checked at implCreateItemEntry, |
428 | | // so it would have been cloned (and would no longer have this |
429 | | // flag). So we can just return here |
430 | 3.31M | return; |
431 | | |
432 | 1.12G | if (SfxItemPool::IsSlot(pSource->Which())) |
433 | 15.4M | { |
434 | | // SlotItems are cloned, so delete |
435 | 15.4M | delete pSource; |
436 | 15.4M | return; |
437 | 15.4M | } |
438 | | |
439 | 1.11G | if (1 < pSource->GetRefCount()) |
440 | 1.04G | { |
441 | | // Still multiple references present, so just alter the RefCount |
442 | 1.04G | pSource->ReleaseRef(); |
443 | 1.04G | return; |
444 | 1.04G | } |
445 | | |
446 | | // try to get an ItemInstanceManager for global Item instance sharing |
447 | 61.6M | ItemInstanceManager* pManager(aInstanceManagerHelper.getExistingItemInstanceManager(*pSource)); |
448 | | |
449 | | // check if we should/can remove this Item from the global |
450 | | // ItemInstanceManager mechanism |
451 | 61.6M | if (nullptr != pManager) |
452 | 17.1M | pManager->remove(*pSource); |
453 | | |
454 | | // decrease RefCnt before deleting (destructor asserts for it and that's |
455 | | // good to find other errors) |
456 | 61.6M | pSource->ReleaseRef(); |
457 | | |
458 | | // delete Item |
459 | 61.6M | delete pSource; |
460 | 61.6M | } |
461 | | |
462 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |