/* -*- 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 #include #include #include static bool g_bDisableItemInstanceManager(getenv("SVL_DISABLE_ITEM_INSTANCE_MANAGER")); static bool g_bShareImmediately(getenv("SVL_SHARE_ITEMS_GLOBALLY_INSTANTLY")); #define NUMBER_OF_UNSHARED_INSTANCES (50) #ifdef DBG_UTIL // > typedef std::unordered_map> HightestUsage; static HightestUsage aHightestUsage; static void addUsage(const SfxPoolItem& rCandidate) { HightestUsage::iterator aHit(aHightestUsage.find(rCandidate.Which())); if (aHit == aHightestUsage.end()) { aHightestUsage.insert({ rCandidate.Which(), { 1, typeid(rCandidate).name() } }); return; } aHit->second.first++; } void listSfxPoolItemsWithHighestUsage(sal_uInt16 nNum) { struct sorted { sal_uInt16 nWhich; sal_uInt32 nUsage; const char* pType; sorted(sal_uInt16 _nWhich, sal_uInt32 _nUsage, const char* _pType) : nWhich(_nWhich) , nUsage(_nUsage) , pType(_pType) { } bool operator<(const sorted& rDesc) const { return nUsage > rDesc.nUsage; } }; std::vector aSorted; aSorted.reserve(aHightestUsage.size()); for (const auto& rEntry : aHightestUsage) aSorted.emplace_back(rEntry.first, rEntry.second.first, rEntry.second.second); std::sort(aSorted.begin(), aSorted.end()); sal_uInt16 a(0); SAL_INFO("svl.items", "ITEM: List of the " << nNum << " SfxPoolItems with highest non-RefCounted usages:"); for (const auto& rEntry : aSorted) { SAL_INFO("svl.items", " ITEM(" << a << "): Which: " << rEntry.nWhich << " Uses: " << rEntry.nUsage << " Type: " << rEntry.pType); if (++a >= nNum) break; } } #endif void DefaultItemInstanceManager::add(const SfxPoolItem& rItem) { maRegistered[rItem.Which()].insert(&rItem); } void DefaultItemInstanceManager::remove(const SfxPoolItem& rItem) { maRegistered[rItem.Which()].erase(&rItem); } // Class that implements global Item sharing. It uses rtti to // associate every Item-derivation with a possible incarnation // of a DefaultItemInstanceManager. This is the default, it will // give direct implementations at the Items that overload // getItemInstanceManager() preference. These are expected to // return static instances of a derived implementation of a // ItemInstanceManager. // All in all there are now the following possibilities to support // this for individual Item derivations: // (1) Do nothing: // In that case, if the Item is shareable, the new mechanism // will kick in: It will start sharing the Item globally, // but not immediately: After a defined amount of allowed // non-shared occurrences (look for NUMBER_OF_UNSHARED_INSTANCES) // an instance of the default ItemInstanceManager, a // DefaultItemInstanceManager, will be incarnated and used. // NOTE: Mixing shared/unshared instances is not a problem (we // might even implement a kind of 're-hash' when this kicks in, // but is not really needed). // (2) Overload getItemInstanceManager for SfxPoolItem in a class // derived from SfxPoolItem and... // (2a) Return a static incarnation of DefaultItemInstanceManager to // immediately start global sharing of that Item derivation. // (2b) Implement and return your own implementation and static // incarnation of ItemInstanceManager to do something better/ // faster that the default implementation can do. Example: // SvxFontItem, uses hashing now. // There are two supported ENVVARs to use: // (a) SVL_DISABLE_ITEM_INSTANCE_MANAGER: // This disables the mechanism of global Item sharing completely. // This can be used to test/check speed/memory needs compared with // using it, but also may come in handy to check if evtl. errors/ // regressions have to do with it. // (b) SVL_SHARE_ITEMS_GLOBALLY_INSTANTLY: // This internally forces the NUMBER_OF_UNSHARED_INSTANCES to be // ignored and start sharing ALL Item derivations instantly. class InstanceManagerHelper { typedef std::unordered_map> managerTypeMap; managerTypeMap maManagerPerType; public: InstanceManagerHelper() {} ~InstanceManagerHelper() { for (auto& rCandidate : maManagerPerType) if (nullptr != rCandidate.second.second) delete rCandidate.second.second; } ItemInstanceManager* getOrCreateItemInstanceManager(const SfxPoolItem& rItem) { // deactivated? if (g_bDisableItemInstanceManager) return nullptr; // Item cannot be shared? if (!rItem.isShareable()) return nullptr; // Prefer getting an ItemInstanceManager directly from // the Item: These are the extra implemented (and thus // hopefully fastest) incarnations ItemInstanceManager* pManager(rItem.getItemInstanceManager()); // Check for correct SfxItemType, there may be derivations of that class. // Note that Managers from the Items are *not* added to local list, // they are expected to be static instances at the Items for fastest access if (nullptr != pManager && pManager->ItemType() == rItem.ItemType()) return pManager; // check local memory for existing entry managerTypeMap::iterator aHit(maManagerPerType.find(rItem.ItemType())); // no instance yet if (aHit == maManagerPerType.end()) { // create a default one to start usage-counting if (g_bShareImmediately) { // create, insert locally and immediately start sharing ItemInstanceManager* pNew; if (rItem.supportsHashCode()) pNew = new HashedItemInstanceManager(rItem.ItemType()); else pNew = new DefaultItemInstanceManager(rItem.ItemType()); maManagerPerType.insert({ rItem.ItemType(), std::make_pair(0, pNew) }); return pNew; } // start countdown from NUMBER_OF_UNSHARED_INSTANCES until zero is reached maManagerPerType.insert( { rItem.ItemType(), std::make_pair(NUMBER_OF_UNSHARED_INSTANCES, nullptr) }); return nullptr; } // if there is already an ItemInstanceManager incarnated, return it if (nullptr != aHit->second.second) return aHit->second.second; if (aHit->second.first > 0) { // still not the needed number of hits, countdown & return nullptr aHit->second.first--; return nullptr; } // here the countdown is zero and there is not yet a ItemInstanceManager // incarnated. Do so, register and return it assert(nullptr == aHit->second.second); ItemInstanceManager* pNew; if (rItem.supportsHashCode()) pNew = new HashedItemInstanceManager(rItem.ItemType()); else pNew = new DefaultItemInstanceManager(rItem.ItemType()); aHit->second.second = pNew; return pNew; } ItemInstanceManager* getExistingItemInstanceManager(const SfxPoolItem& rItem) { // deactivated? if (g_bDisableItemInstanceManager) return nullptr; // Item cannot be shared? if (!rItem.isShareable()) return nullptr; // Prefer getting an ItemInstanceManager directly from // the Item: These are the extra implemented (and thus // hopefully fastest) incarnations ItemInstanceManager* pManager(rItem.getItemInstanceManager()); // Check for correct SfxItemType, there may be derivations of that class. // Note that Managers from the Items are *not* added to local list, // they are expected to be static instances at the Items if (nullptr != pManager && pManager->ItemType() == rItem.ItemType()) return pManager; // check local memory for existing entry managerTypeMap::iterator aHit(maManagerPerType.find(rItem.ItemType())); if (aHit == maManagerPerType.end()) // no instance yet, return nullptr return nullptr; // if there is already a ItemInstanceManager incarnated, return it if (nullptr != aHit->second.second) return aHit->second.second; // count-up needed number of hits again if item is released if (aHit->second.first < NUMBER_OF_UNSHARED_INSTANCES) aHit->second.first++; return nullptr; } }; // the single static instance that takes over that global Item sharing static InstanceManagerHelper aInstanceManagerHelper; SfxPoolItem const* implCreateItemEntry(SfxItemPool& rPool, SfxPoolItem const* pSource, bool bPassingOwnership) { if (nullptr == pSource) // SfxItemState::UNKNOWN aka current default (nullptr) // just use/return nullptr return nullptr; if (pSource->isStaticDefault()) // static default Items can just be used without RefCounting // NOTE: This now includes IsInvalidItem/IsDisabledItem return pSource; if (0 == pSource->Which()) { // There should be no Items with 0 == WhichID, but there are some // constructed for dialog return values AKA result (look for SetReturnValue) // these need to be cloned (currently...) if (bPassingOwnership) return pSource; return pSource->Clone(); } if (pSource->isDynamicDefault() && rPool.GetPoolDefaultItem(pSource->Which()) == pSource) // dynamic defaults are not allowed to 'leave' the Pool they are // defined for. We can check by comparing the PoolDefault (the // PoolDefaultItem) to pSource by ptr compare (instance). When // same Item we can use without RefCount. Else it will be cloned // below the standard way. return pSource; #ifdef DBG_UTIL // remember WhichID due to being able to assert Clone() error(s) const sal_uInt16 nWhich(pSource->Which()); #endif if (SfxItemPool::IsSlot(pSource->Which())) { // SlotItems were always cloned in original (even when bPassingOwnership), // so do that here, too (but without bPassingOwnership). // They do not need to be registered at pool (actually impossible, pools // do not have entries for SlotItems) so handle here early if (!bPassingOwnership) { pSource = pSource->Clone(rPool.GetMasterPool()); // ARGH! Found out that *some* ::Clone implementations fail to also clone the // WhichID set at the original Item, e.g. SfxFrameItem. Assert, this is an error #ifdef DBG_UTIL assert(pSource->Which() == nWhich && "ITEM: Clone of Item did NOT copy/set WhichID (!)"); #endif } return pSource; } // get the pool with which ItemSets have to work, plus get the // pool at which the WhichID is defined, so calls to it do not // have to do this repeatedly SfxItemPool* pMasterPool(rPool.GetMasterPool()); assert(nullptr != pMasterPool); // The Item itself is shareable when it is used/added at an instance // that RefCounts the Item, SfxItemPool or SfxPoolItemHolder. Try // to share items that are already shared if (pSource->GetRefCount() > 0) { if (pSource->isShareable()) { // SfxSetItems cannot be shared if they are in/use another pool if (!pSource->isSetItem() || static_cast(pSource)->GetItemSet().GetPool() == pMasterPool) { // If we get here we can share the Item pSource->AddRef(); return pSource; } } } // try to get an ItemInstanceManager for global Item instance sharing ItemInstanceManager* pManager(aInstanceManagerHelper.getOrCreateItemInstanceManager(*pSource)); // check if we can globally share the Item using an ItemInstanceManager if (pManager) { const SfxPoolItem* pAlternative(pManager->find(*pSource)); if (pAlternative) { // Here we do *not* need to check if it is an SfxSetItem // and cannot be shared if they are in/use another pool: // The SfxItemSet::operator== will check for SfxItemPools // being equal, thus when found in global share the Pool // cannot be equal // need to delete evtl. handed over ownership change Item if (bPassingOwnership) delete pSource; // If we get here we can share the Item pAlternative->AddRef(); return pAlternative; } } // check if the handed over and to be directly used item is a // SfxSetItem, that would make it pool-dependent. It then must have // the same target-pool, ensure that by the cost of cloning it // (should not happen) if (bPassingOwnership && pSource->isSetItem() && static_cast(pSource)->GetItemSet().GetPool() != pMasterPool) { const SfxPoolItem* pOld(pSource); pSource = pSource->Clone(pMasterPool); #ifdef DBG_UTIL assert(pSource->Which() == nWhich && "ITEM: Clone of Item did NOT copy/set WhichID (!)"); #endif delete pOld; } #ifdef DBG_UTIL // create statistics for listSfxPoolItemsWithHighestUsage addUsage(*pSource); #endif // when we reach this line we know that we have to add/create a new item. If // bPassingOwnership is given just use the item, else clone it if (!bPassingOwnership) { auto pPreviousSource = pSource; pSource = pSource->Clone(pMasterPool); #ifdef DBG_UTIL assert(pSource->Which() == nWhich && "ITEM: Clone of Item did NOT copy/set WhichID (!)"); #endif SAL_WARN_IF(typeid(*pPreviousSource) != typeid(*pSource), "svl", "wrong item from Clone(), expected " << typeid(*pPreviousSource).name() << " but got " << typeid(*pSource).name()); assert(typeid(*pPreviousSource) == typeid(*pSource) && "wrong item from Clone()"); } // increase RefCnt 0->1 pSource->AddRef(); // check if we should register this Item for the global // ItemInstanceManager mechanism (only for shareable Items) if (nullptr != pManager) pManager->add(*pSource); return pSource; } void implCleanupItemEntry(const SfxPoolItem* pSource) { if (nullptr == pSource) // no entry, done return; if (pSource->isStaticDefault()) // static default Items can just be used without RefCounting // NOTE: This now includes IsInvalidItem/IsDisabledItem return; if (0 == pSource->Which()) { // There should be no Items with 0 == WhichID, but there are some // constructed for dialog return values AKA result (look for SetReturnValue) // and need to be deleted delete pSource; return; } if (pSource->isDynamicDefault()) // dynamic default Items can only be used without RefCounting // when same pool. this is already checked at implCreateItemEntry, // so it would have been cloned (and would no longer have this // flag). So we can just return here return; if (SfxItemPool::IsSlot(pSource->Which())) { // SlotItems are cloned, so delete delete pSource; return; } if (1 < pSource->GetRefCount()) { // Still multiple references present, so just alter the RefCount pSource->ReleaseRef(); return; } // try to get an ItemInstanceManager for global Item instance sharing ItemInstanceManager* pManager(aInstanceManagerHelper.getExistingItemInstanceManager(*pSource)); // check if we should/can remove this Item from the global // ItemInstanceManager mechanism if (nullptr != pManager) pManager->remove(*pSource); // decrease RefCnt before deleting (destructor asserts for it and that's // good to find other errors) pSource->ReleaseRef(); // delete Item delete pSource; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */