diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sd/source/ui/slidesorter/cache | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sd/source/ui/slidesorter/cache')
21 files changed, 3458 insertions, 0 deletions
diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapCache.cxx b/sd/source/ui/slidesorter/cache/SlsBitmapCache.cxx new file mode 100644 index 000000000..87c727408 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsBitmapCache.cxx @@ -0,0 +1,550 @@ +/* -*- 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 <memory> +#include <unordered_map> +#include "SlsBitmapCache.hxx" +#include "SlsCacheCompactor.hxx" +#include "SlsBitmapCompressor.hxx" +#include "SlsCacheConfiguration.hxx" + +#include <sal/log.hxx> + +// Define the default value for the maximal cache size that is used for +// previews that are currently not visible. The visible previews are all +// held in memory at all times. This default is used only when the +// configuration does not have a value. +const sal_Int32 MAXIMAL_CACHE_SIZE = 4*1024L*1024L; + +using namespace ::com::sun::star::uno; + +namespace sd::slidesorter::cache { + +class BitmapCache::CacheEntry +{ +public: + CacheEntry(const BitmapEx& rBitmap, sal_Int32 nLastAccessTime, bool bIsPrecious); + CacheEntry(sal_Int32 nLastAccessTime, bool bIsPrecious); + inline void Recycle (const CacheEntry& rEntry); + inline sal_Int32 GetMemorySize() const; + void Compress (const std::shared_ptr<BitmapCompressor>& rpCompressor); + inline void Decompress(); + + bool IsUpToDate() const { return mbIsUpToDate; } + void SetUpToDate (bool bIsUpToDate) { mbIsUpToDate = bIsUpToDate; } + sal_Int32 GetAccessTime() const { return mnLastAccessTime; } + void SetAccessTime (sal_Int32 nAccessTime) { mnLastAccessTime = nAccessTime; } + + const BitmapEx& GetPreview() const { return maPreview; } + inline void SetPreview (const BitmapEx& rPreview); + bool HasPreview() const; + + const BitmapEx& GetMarkedPreview() const { return maMarkedPreview; } + inline void SetMarkedPreview (const BitmapEx& rMarkePreview); + + bool HasReplacement() const { return (mpReplacement != nullptr); } + inline bool HasLosslessReplacement() const; + void Invalidate() { mpReplacement.reset(); mpCompressor.reset(); mbIsUpToDate = false; } + bool IsPrecious() const { return mbIsPrecious; } + void SetPrecious (bool bIsPrecious) { mbIsPrecious = bIsPrecious; } + +private: + BitmapEx maPreview; + BitmapEx maMarkedPreview; + std::shared_ptr<BitmapReplacement> mpReplacement; + std::shared_ptr<BitmapCompressor> mpCompressor; + bool mbIsUpToDate; + sal_Int32 mnLastAccessTime; + // When this flag is set then the bitmap is not modified by a cache + // compactor. + bool mbIsPrecious; +}; + +namespace { + +class CacheHash { +public: + size_t operator()(const BitmapCache::CacheKey& p) const + { return reinterpret_cast<size_t>(p); } +}; + +} + +class BitmapCache::CacheBitmapContainer + : public std::unordered_map<CacheKey, CacheEntry, CacheHash> +{ +public: + CacheBitmapContainer() {} +}; + +namespace { + +typedef ::std::vector< + ::std::pair< ::sd::slidesorter::cache::BitmapCache::CacheKey, + ::sd::slidesorter::cache::BitmapCache::CacheEntry> + > SortableBitmapContainer; + + /** Compare elements of the bitmap cache according to their last access + time. + */ + class AccessTimeComparator + { + public: + bool operator () ( + const SortableBitmapContainer::value_type& e1, + const SortableBitmapContainer::value_type& e2) + { + return e1.second.GetAccessTime() < e2.second.GetAccessTime(); + } + }; + +} // end of anonymous namespace + +//===== BitmapCache ========================================================= + +BitmapCache::BitmapCache () + : mpBitmapContainer(new CacheBitmapContainer()), + mnNormalCacheSize(0), + mnPreciousCacheSize(0), + mnCurrentAccessTime(0), + mnMaximalNormalCacheSize(MAXIMAL_CACHE_SIZE), + mbIsFull(false) +{ + Any aCacheSize (CacheConfiguration::Instance()->GetValue("CacheSize")); + if (aCacheSize.has<sal_Int32>()) + aCacheSize >>= mnMaximalNormalCacheSize; + + mpCacheCompactor = CacheCompactor::Create(*this,mnMaximalNormalCacheSize); +} + +BitmapCache::~BitmapCache() +{ + Clear(); +} + +void BitmapCache::Clear() +{ + ::osl::MutexGuard aGuard (maMutex); + + mpBitmapContainer->clear(); + mnNormalCacheSize = 0; + mnPreciousCacheSize = 0; + mnCurrentAccessTime = 0; +} + +bool BitmapCache::HasBitmap (const CacheKey& rKey) +{ + ::osl::MutexGuard aGuard (maMutex); + + CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); + return (iEntry != mpBitmapContainer->end() + && (iEntry->second.HasPreview() || iEntry->second.HasReplacement())); +} + +bool BitmapCache::BitmapIsUpToDate (const CacheKey& rKey) +{ + ::osl::MutexGuard aGuard (maMutex); + + bool bIsUpToDate = false; + CacheBitmapContainer::iterator aIterator (mpBitmapContainer->find(rKey)); + if (aIterator != mpBitmapContainer->end()) + bIsUpToDate = aIterator->second.IsUpToDate(); + + return bIsUpToDate; +} + +BitmapEx BitmapCache::GetBitmap (const CacheKey& rKey) +{ + ::osl::MutexGuard aGuard (maMutex); + + CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); + if (iEntry == mpBitmapContainer->end()) + { + // Create an empty bitmap for the given key that acts as placeholder + // until we are given the real one. Mark it as not being up to date. + SetBitmap(rKey, BitmapEx(), false); + iEntry = mpBitmapContainer->find(rKey); + iEntry->second.SetUpToDate(false); + } + else + { + iEntry->second.SetAccessTime(mnCurrentAccessTime++); + + // Maybe we have to decompress the preview. + if ( ! iEntry->second.HasPreview() && iEntry->second.HasReplacement()) + { + UpdateCacheSize(iEntry->second, REMOVE); + iEntry->second.Decompress(); + UpdateCacheSize(iEntry->second, ADD); + } + } + return iEntry->second.GetPreview(); +} + +BitmapEx BitmapCache::GetMarkedBitmap (const CacheKey& rKey) +{ + ::osl::MutexGuard aGuard (maMutex); + + CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); + if (iEntry != mpBitmapContainer->end()) + { + iEntry->second.SetAccessTime(mnCurrentAccessTime++); + return iEntry->second.GetMarkedPreview(); + } + else + return BitmapEx(); +} + +void BitmapCache::ReleaseBitmap (const CacheKey& rKey) +{ + ::osl::MutexGuard aGuard (maMutex); + + CacheBitmapContainer::iterator aIterator (mpBitmapContainer->find(rKey)); + if (aIterator != mpBitmapContainer->end()) + { + UpdateCacheSize(aIterator->second, REMOVE); + mpBitmapContainer->erase(aIterator); + } +} + +bool BitmapCache::InvalidateBitmap (const CacheKey& rKey) +{ + ::osl::MutexGuard aGuard (maMutex); + + CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); + if (iEntry != mpBitmapContainer->end()) + { + iEntry->second.SetUpToDate(false); + + // When there is a preview then we release the replacement. The + // preview itself is kept until a new one is created. + if (iEntry->second.HasPreview()) + { + UpdateCacheSize(iEntry->second, REMOVE); + iEntry->second.Invalidate(); + UpdateCacheSize(iEntry->second, ADD); + } + return true; + } + else + return false; +} + +void BitmapCache::InvalidateCache() +{ + ::osl::MutexGuard aGuard (maMutex); + + for (auto& rEntry : *mpBitmapContainer) + { + rEntry.second.Invalidate(); + } + ReCalculateTotalCacheSize(); +} + +void BitmapCache::SetBitmap ( + const CacheKey& rKey, + const BitmapEx& rPreview, + bool bIsPrecious) +{ + ::osl::MutexGuard aGuard (maMutex); + + CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); + if (iEntry != mpBitmapContainer->end()) + { + UpdateCacheSize(iEntry->second, REMOVE); + iEntry->second.SetPreview(rPreview); + iEntry->second.SetUpToDate(true); + iEntry->second.SetAccessTime(mnCurrentAccessTime++); + } + else + { + iEntry = mpBitmapContainer->emplace( + rKey, + CacheEntry(rPreview, mnCurrentAccessTime++, bIsPrecious) + ).first; + } + + if (iEntry != mpBitmapContainer->end()) + UpdateCacheSize(iEntry->second, ADD); +} + +void BitmapCache::SetMarkedBitmap ( + const CacheKey& rKey, + const BitmapEx& rPreview) +{ + ::osl::MutexGuard aGuard (maMutex); + + CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); + if (iEntry != mpBitmapContainer->end()) + { + UpdateCacheSize(iEntry->second, REMOVE); + iEntry->second.SetMarkedPreview(rPreview); + iEntry->second.SetAccessTime(mnCurrentAccessTime++); + UpdateCacheSize(iEntry->second, ADD); + } +} + +void BitmapCache::SetPrecious (const CacheKey& rKey, bool bIsPrecious) +{ + ::osl::MutexGuard aGuard (maMutex); + + CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); + if (iEntry != mpBitmapContainer->end()) + { + if (iEntry->second.IsPrecious() != bIsPrecious) + { + UpdateCacheSize(iEntry->second, REMOVE); + iEntry->second.SetPrecious(bIsPrecious); + UpdateCacheSize(iEntry->second, ADD); + } + } + else if (bIsPrecious) + { + iEntry = mpBitmapContainer->emplace( + rKey, + CacheEntry(BitmapEx(), mnCurrentAccessTime++, bIsPrecious) + ).first; + UpdateCacheSize(iEntry->second, ADD); + } +} + +void BitmapCache::ReCalculateTotalCacheSize() +{ + ::osl::MutexGuard aGuard (maMutex); + + mnNormalCacheSize = 0; + mnPreciousCacheSize = 0; + for (const auto& rEntry : *mpBitmapContainer) + { + if (rEntry.second.IsPrecious()) + mnPreciousCacheSize += rEntry.second.GetMemorySize(); + else + mnNormalCacheSize += rEntry.second.GetMemorySize(); + } + mbIsFull = (mnNormalCacheSize >= mnMaximalNormalCacheSize); + + SAL_INFO("sd.sls", __func__ << ": cache size is " << mnNormalCacheSize << "/" << mnPreciousCacheSize); +} + +void BitmapCache::Recycle (const BitmapCache& rCache) +{ + ::osl::MutexGuard aGuard (maMutex); + + for (const auto& rOtherEntry : *rCache.mpBitmapContainer) + { + CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rOtherEntry.first)); + if (iEntry == mpBitmapContainer->end()) + { + iEntry = mpBitmapContainer->emplace( + rOtherEntry.first, + CacheEntry(mnCurrentAccessTime++, true) + ).first; + UpdateCacheSize(iEntry->second, ADD); + } + if (iEntry != mpBitmapContainer->end()) + { + UpdateCacheSize(iEntry->second, REMOVE); + iEntry->second.Recycle(rOtherEntry.second); + UpdateCacheSize(iEntry->second, ADD); + } + } +} + +BitmapCache::CacheIndex BitmapCache::GetCacheIndex() const +{ + ::osl::MutexGuard aGuard (maMutex); + + // Create a copy of the bitmap container. + SortableBitmapContainer aSortedContainer; + aSortedContainer.reserve(mpBitmapContainer->size()); + + // Copy the relevant entries. + for (const auto& rEntry : *mpBitmapContainer) + { + if ( rEntry.second.IsPrecious()) + continue; + + if ( ! rEntry.second.HasPreview()) + continue; + + aSortedContainer.emplace_back(rEntry.first, rEntry.second); + } + + // Sort the remaining entries. + ::std::sort(aSortedContainer.begin(), aSortedContainer.end(), AccessTimeComparator()); + + // Return a list with the keys of the sorted entries. + CacheIndex aIndex; + aIndex.reserve(aSortedContainer.size()); + for (const auto& rIndexEntry : aSortedContainer) + aIndex.push_back(rIndexEntry.first); + return aIndex; +} + +void BitmapCache::Compress ( + const CacheKey& rKey, + const std::shared_ptr<BitmapCompressor>& rpCompressor) +{ + ::osl::MutexGuard aGuard (maMutex); + + CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey)); + if (iEntry != mpBitmapContainer->end() && iEntry->second.HasPreview()) + { + UpdateCacheSize(iEntry->second, REMOVE); + iEntry->second.Compress(rpCompressor); + UpdateCacheSize(iEntry->second, ADD); + } +} + +void BitmapCache::UpdateCacheSize (const CacheEntry& rEntry, CacheOperation eOperation) +{ + sal_Int32 nEntrySize (rEntry.GetMemorySize()); + sal_Int32& rCacheSize (rEntry.IsPrecious() ? mnPreciousCacheSize : mnNormalCacheSize); + switch (eOperation) + { + case ADD: + rCacheSize += nEntrySize; + if ( ! rEntry.IsPrecious() && mnNormalCacheSize>mnMaximalNormalCacheSize) + { + mbIsFull = true; + SAL_INFO("sd.sls", __func__ << ": cache size is " << mnNormalCacheSize << " > " << mnMaximalNormalCacheSize); + mpCacheCompactor->RequestCompaction(); + } + break; + + case REMOVE: + rCacheSize -= nEntrySize; + if (mnNormalCacheSize < mnMaximalNormalCacheSize) + mbIsFull = false; + break; + + default: + assert(false); + break; + } +} + +//===== CacheEntry ============================================================ + +BitmapCache::CacheEntry::CacheEntry( + sal_Int32 nLastAccessTime, + bool bIsPrecious) + : mbIsUpToDate(true), + mnLastAccessTime(nLastAccessTime), + mbIsPrecious(bIsPrecious) +{ +} + +BitmapCache::CacheEntry::CacheEntry( + const BitmapEx& rPreview, + sal_Int32 nLastAccessTime, + bool bIsPrecious) + : maPreview(rPreview), + mbIsUpToDate(true), + mnLastAccessTime(nLastAccessTime), + mbIsPrecious(bIsPrecious) +{ +} + +inline void BitmapCache::CacheEntry::Recycle (const CacheEntry& rEntry) +{ + if ((rEntry.HasPreview() || rEntry.HasLosslessReplacement()) + && ! (HasPreview() || HasLosslessReplacement())) + { + maPreview = rEntry.maPreview; + maMarkedPreview = rEntry.maMarkedPreview; + mpReplacement = rEntry.mpReplacement; + mpCompressor = rEntry.mpCompressor; + mnLastAccessTime = rEntry.mnLastAccessTime; + mbIsUpToDate = rEntry.mbIsUpToDate; + } +} + +inline sal_Int32 BitmapCache::CacheEntry::GetMemorySize() const +{ + sal_Int32 nSize (0); + nSize += maPreview.GetSizeBytes(); + nSize += maMarkedPreview.GetSizeBytes(); + if (mpReplacement != nullptr) + nSize += mpReplacement->GetMemorySize(); + return nSize; +} + +void BitmapCache::CacheEntry::Compress (const std::shared_ptr<BitmapCompressor>& rpCompressor) +{ + if ( maPreview.IsEmpty()) + return; + + if (mpReplacement == nullptr) + { + mpReplacement = rpCompressor->Compress(maPreview); + +#ifdef DEBUG_SD_SLSBITMAPCACHE + sal_uInt32 nOldSize (maPreview.GetSizeBytes()); + sal_uInt32 nNewSize (mpReplacement.get()!=NULL ? mpReplacement->GetMemorySize() : 0); + if (nOldSize == 0) + nOldSize = 1; + sal_Int32 nRatio (100L * nNewSize / nOldSize); + SAL_INFO("sd.sls", __func__ << ": compressing bitmap for " << %x << " from " << nOldSize << " to " << nNewSize << " bytes (" << nRatio << "%)"); +#endif + + mpCompressor = rpCompressor; + } + + maPreview.SetEmpty(); + maMarkedPreview.SetEmpty(); +} + +inline void BitmapCache::CacheEntry::Decompress() +{ + if (mpReplacement != nullptr && mpCompressor != nullptr && maPreview.IsEmpty()) + { + maPreview = mpCompressor->Decompress(*mpReplacement); + maMarkedPreview.SetEmpty(); + if ( ! mpCompressor->IsLossless()) + mbIsUpToDate = false; + } +} + +inline void BitmapCache::CacheEntry::SetPreview (const BitmapEx& rPreview) +{ + maPreview = rPreview; + maMarkedPreview.SetEmpty(); + mpReplacement.reset(); + mpCompressor.reset(); +} + +bool BitmapCache::CacheEntry::HasPreview() const +{ + return ! maPreview.IsEmpty(); +} + +inline void BitmapCache::CacheEntry::SetMarkedPreview (const BitmapEx& rMarkedPreview) +{ + maMarkedPreview = rMarkedPreview; +} + +inline bool BitmapCache::CacheEntry::HasLosslessReplacement() const +{ + return mpReplacement != nullptr && mpCompressor != nullptr && mpCompressor->IsLossless(); +} + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapCache.hxx b/sd/source/ui/slidesorter/cache/SlsBitmapCache.hxx new file mode 100644 index 000000000..98b0bcb53 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsBitmapCache.hxx @@ -0,0 +1,208 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/bitmapex.hxx> +#include <osl/mutex.hxx> +#include <memory> + +class SdrPage; + +namespace sd::slidesorter::cache +{ +class CacheCompactor; +class BitmapCompressor; + +/** This low level cache is the actual bitmap container. It supports a + precious flag for every preview bitmap and keeps track of total sizes + for all previews with/without this flag. The precious flag is used by + compaction algorithms to determine which previews may be compressed or + even discarded and which have to remain in their original form. The + precious flag is usually set for the visible previews. + + Additionally to the actual preview there is an optional marked preview. + This is used for slides excluded from the slide show which have a preview + that shows a mark (some sort of bitmap overlay) to that effect. +*/ +class BitmapCache +{ +public: + /** The key for looking up preview bitmaps is a pointer to an SdrPage + object. The prior use of PageObjectViewObjectContact objects (which + ultimately use them) turned out to be less suitable because their + life time is shorter then that of the page objects. Frequent + destruction and re-creation of the preview bitmaps was the result. + */ + typedef const SdrPage* CacheKey; + class CacheEntry; + class CacheBitmapContainer; + typedef ::std::vector<CacheKey> CacheIndex; + + /** Create a new cache for bitmap objects. + The default value from the configuration is used. + When that does not exist then an internal default value is + used. + */ + explicit BitmapCache(); + + /** The destructor clears the cache and releases all bitmaps still in it. + */ + ~BitmapCache(); + + /** Remove all preview bitmaps from the cache. After this call the + cache is empty. + */ + void Clear(); + + /** Return <TRUE/> when the cache is full, i.e. the cache compactor had + to be run. + */ + bool IsFull() const { return mbIsFull; } + + /** Return the memory size that is occupied by all non-precious bitmaps + in the cache. + */ + sal_Int32 GetSize() const { return mnNormalCacheSize; } + + /** Return <TRUE/> when a preview bitmap exists for the given key. + */ + bool HasBitmap(const CacheKey& rKey); + + /** Return <TRUE/> when a preview bitmap exists for the given key and + when it is up-to-date. + */ + bool BitmapIsUpToDate(const CacheKey& rKey); + + /** Return the preview bitmap for the given contact object. + */ + BitmapEx GetBitmap(const CacheKey& rKey); + + /** Return the marked preview bitmap for the given contact object. + */ + BitmapEx GetMarkedBitmap(const CacheKey& rKey); + + /** Release the reference to the preview bitmap that is associated with + the given key. + */ + void ReleaseBitmap(const CacheKey& rKey); + + /** Mark the specified preview bitmap as not being up-to-date + anymore. + @return + When the key references a page in the cache then + return <TRUE/>. When the key is not known then <FALSE/> + is returned. + */ + bool InvalidateBitmap(const CacheKey& rKey); + + /** Mark all preview bitmaps as not being up-to-date anymore. + */ + void InvalidateCache(); + + /** Add or replace a bitmap for the given key. + */ + void SetBitmap(const CacheKey& rKey, const BitmapEx& rPreview, bool bIsPrecious); + + /** Add or replace a marked bitmap for the given key. + */ + void SetMarkedBitmap(const CacheKey& rKey, const BitmapEx& rPreview); + + /** Mark the specified preview bitmap as precious, i.e. that it must not + be compressed or otherwise removed from the cache. + */ + void SetPrecious(const CacheKey& rKey, bool bIsPrecious); + + /** Calculate the cache size. This should rarely be necessary because + the cache size is tracked with each modification of preview + bitmaps. + */ + void ReCalculateTotalCacheSize(); + + /** Use the previews in the given cache to initialize missing previews. + */ + void Recycle(const BitmapCache& rCache); + + /** Return a list of sorted cache keys that represent an index into (a + part of) the cache. The entries of the index are sorted according + to last access times with the least recently access time first. + Entries with the precious flag set are omitted. + Entries with that have no preview bitmaps are omitted. + */ + CacheIndex GetCacheIndex() const; + + /** Compress the specified preview bitmap with the given bitmap + compressor. A reference to the compressor is stored for later + decompression. + */ + void Compress(const CacheKey& rKey, const std::shared_ptr<BitmapCompressor>& rpCompressor); + +private: + mutable ::osl::Mutex maMutex; + + std::unique_ptr<CacheBitmapContainer> mpBitmapContainer; + + /** Total size of bytes that are occupied by bitmaps in the cache for + whom the slides are currently not inside the visible area. + */ + sal_Int32 mnNormalCacheSize; + + /** Total size of bytes that are occupied by bitmaps in the cache for + whom the slides are currently visible. + */ + sal_Int32 mnPreciousCacheSize; + + /** At the moment the access time is not an actual time or date value + but a counter that is increased with every access. It thus defines + the same ordering as a true time. + */ + sal_Int32 mnCurrentAccessTime; + + /** The maximal cache size for the off-screen preview bitmaps. When + mnNormalCacheSize grows larger than this value then the + mpCacheCompactor member is used to reduce the cache size. + */ + sal_Int32 mnMaximalNormalCacheSize; + + /** The cache compactor is used to reduce the number of bytes used by + off-screen preview bitmaps. + */ + ::std::unique_ptr<CacheCompactor> mpCacheCompactor; + + /** This flag stores if the cache is or recently was full, i.e. the + cache compactor has or had to be run in order to reduce the cache + size to the allowed value. + */ + bool mbIsFull; + + /** Update mnNormalCacheSize or mnPreciousCacheSize according to the + precious flag of the specified preview bitmap and the specified + operation. + */ + enum CacheOperation + { + ADD, + REMOVE + }; + void UpdateCacheSize(const CacheEntry& rKey, CacheOperation eOperation); +}; + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.cxx b/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.cxx new file mode 100644 index 000000000..d4da935dd --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.cxx @@ -0,0 +1,197 @@ +/* -*- 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 "SlsBitmapCompressor.hxx" + +#include <tools/stream.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/filter/PngImageReader.hxx> +#include <vcl/pngwrite.hxx> + +namespace sd::slidesorter::cache { + +//===== NoBitmapCompression =================================================== + +/** This dummy replacement simply stores a shared pointer to the original + preview bitmap. +*/ +class NoBitmapCompression::DummyReplacement + : public BitmapReplacement +{ +public: + BitmapEx maPreview; + + explicit DummyReplacement (const BitmapEx& rPreview) : maPreview(rPreview) { } + virtual ~DummyReplacement() {} + virtual sal_Int32 GetMemorySize() const override { return maPreview.GetSizeBytes(); } +}; + +std::shared_ptr<BitmapReplacement> NoBitmapCompression::Compress (const BitmapEx& rBitmap) const +{ + return std::make_shared<DummyReplacement>(rBitmap); +} + +BitmapEx NoBitmapCompression::Decompress (const BitmapReplacement& rBitmapData) const +{ + return dynamic_cast<const DummyReplacement&>(rBitmapData).maPreview; +} + +bool NoBitmapCompression::IsLossless() const +{ + return true; +} + +//===== CompressionByDeletion ================================================= + +std::shared_ptr<BitmapReplacement> CompressionByDeletion::Compress (const BitmapEx& ) const +{ + return std::shared_ptr<BitmapReplacement>(); +} + +BitmapEx CompressionByDeletion::Decompress (const BitmapReplacement& ) const +{ + // Return a NULL pointer. This will eventually lead to a request for + // the creation of a new one. + return BitmapEx(); +} + +bool CompressionByDeletion::IsLossless() const +{ + return false; +} + +//===== ResolutionReduction =================================================== + +/** Store a scaled down bitmap together with the original size. +*/ +class ResolutionReduction::ResolutionReducedReplacement : public BitmapReplacement +{ +public: + BitmapEx maPreview; + Size maOriginalSize; + + virtual ~ResolutionReducedReplacement(); + virtual sal_Int32 GetMemorySize() const override; +}; + +ResolutionReduction::ResolutionReducedReplacement::~ResolutionReducedReplacement() +{ +} + +sal_Int32 ResolutionReduction::ResolutionReducedReplacement::GetMemorySize() const +{ + return maPreview.GetSizeBytes(); +} + +std::shared_ptr<BitmapReplacement> ResolutionReduction::Compress ( + const BitmapEx& rBitmap) const +{ + auto pResult = std::make_shared<ResolutionReducedReplacement>(); + pResult->maPreview = rBitmap; + Size aSize (rBitmap.GetSizePixel()); + pResult->maOriginalSize = aSize; + if (aSize.Width()>0 && aSize.Width()<mnWidth) + { + int nHeight = aSize.Height() * mnWidth / aSize.Width() ; + pResult->maPreview.Scale(Size(mnWidth,nHeight)); + } + + return pResult; +} + +BitmapEx ResolutionReduction::Decompress (const BitmapReplacement& rBitmapData) const +{ + BitmapEx aResult; + + const ResolutionReducedReplacement* pData ( + dynamic_cast<const ResolutionReducedReplacement*>(&rBitmapData)); + + if ( pData && ! pData->maPreview.IsEmpty()) + { + aResult = pData->maPreview; + if (pData->maOriginalSize.Width() > mnWidth) + aResult.Scale(pData->maOriginalSize); + } + + return aResult; +} + +bool ResolutionReduction::IsLossless() const +{ + return false; +} + +//===== PNGCompression ======================================================== + +class PngCompression::PngReplacement : public BitmapReplacement +{ +public: + void* mpData; + sal_Int32 mnDataSize; + PngReplacement() + : mpData(nullptr), + mnDataSize(0) + {} + virtual ~PngReplacement() + { + delete [] static_cast<char*>(mpData); + } + virtual sal_Int32 GetMemorySize() const override + { + return mnDataSize; + } +}; + +std::shared_ptr<BitmapReplacement> PngCompression::Compress (const BitmapEx& rBitmap) const +{ + vcl::PNGWriter aWriter(rBitmap); + SvMemoryStream aStream (32768, 32768); + aWriter.Write(aStream); + + auto pResult = std::make_shared<PngReplacement>(); + pResult->mnDataSize = aStream.Tell(); + pResult->mpData = new char[pResult->mnDataSize]; + memcpy(pResult->mpData, aStream.GetData(), pResult->mnDataSize); + + return pResult; +} + +BitmapEx PngCompression::Decompress ( + const BitmapReplacement& rBitmapData) const +{ + BitmapEx aResult; + const PngReplacement* pData (dynamic_cast<const PngReplacement*>(&rBitmapData)); + if (pData != nullptr) + { + SvMemoryStream aStream (pData->mpData, pData->mnDataSize, StreamMode::READ); + vcl::PngImageReader aReader (aStream); + aResult = aReader.read().GetBitmap(); + } + + return aResult; +} + +bool PngCompression::IsLossless() const +{ + return true; +} + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.hxx b/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.hxx new file mode 100644 index 000000000..4754bead9 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.hxx @@ -0,0 +1,138 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/types.h> +#include <memory> + +class BitmapEx; + +namespace sd::slidesorter::cache +{ +class BitmapReplacement; + +/** This interface class provides the minimal method set for classes that + implement the compression and decompression of preview bitmaps. +*/ +class BitmapCompressor +{ +public: + /** Compress the given bitmap into a replacement format that is specific + to the compressor class. + */ + virtual std::shared_ptr<BitmapReplacement> Compress(const BitmapEx& rBitmap) const = 0; + + /** Decompress the given replacement data into a preview bitmap. + Depending on the compression technique the returned bitmap may + differ from the original bitmap given to the Compress() method. It + may even of the wrong size or empty or the NULL pointer. It is the + task of the caller to create a new preview bitmap if the returned + one is not as desired. + */ + virtual BitmapEx Decompress(const BitmapReplacement& rBitmapData) const = 0; + + /** Return whether the compression and decompression is lossless. This + value is used by the caller of Decompress() to decide whether to use + the returned bitmap as is or if a new preview has to be created. + */ + virtual bool IsLossless() const = 0; + +protected: + ~BitmapCompressor() {} +}; + +/** Interface for preview bitmap replacements. Each bitmap + compressor/decompressor has to provide an implementation that is + suitable to store the compressed bitmaps. +*/ +class BitmapReplacement +{ +public: + virtual sal_Int32 GetMemorySize() const { return 0; } + +protected: + ~BitmapReplacement() {} +}; + +/** This is one trivial bitmap compressor. It stores bitmaps unmodified + instead of compressing them. + This compressor is lossless. +*/ +class NoBitmapCompression : public BitmapCompressor +{ + class DummyReplacement; + +public: + virtual ~NoBitmapCompression() {} + virtual std::shared_ptr<BitmapReplacement> Compress(const BitmapEx& rpBitmap) const override; + virtual BitmapEx Decompress(const BitmapReplacement& rBitmapData) const override; + virtual bool IsLossless() const override; +}; + +/** This is another trivial bitmap compressor. Instead of compressing a + bitmap, it throws the bitmap away. Its Decompress() method returns a + NULL pointer. The caller has to create a new preview bitmap instead. + This compressor clearly is not lossless. +*/ +class CompressionByDeletion : public BitmapCompressor +{ +public: + virtual ~CompressionByDeletion() {} + virtual std::shared_ptr<BitmapReplacement> Compress(const BitmapEx& rBitmap) const override; + virtual BitmapEx Decompress(const BitmapReplacement& rBitmapData) const override; + virtual bool IsLossless() const override; +}; + +/** Compress a preview bitmap by reducing its resolution. While the aspect + ratio is maintained the horizontal resolution is scaled down to 100 + pixels. + This compressor is not lossless. +*/ +class ResolutionReduction : public BitmapCompressor +{ + class ResolutionReducedReplacement; + static const sal_Int32 mnWidth = 100; + +public: + virtual ~ResolutionReduction() {} + virtual std::shared_ptr<BitmapReplacement> Compress(const BitmapEx& rpBitmap) const override; + /** Scale the replacement bitmap up to the original size. + */ + virtual BitmapEx Decompress(const BitmapReplacement& rBitmapData) const override; + virtual bool IsLossless() const override; +}; + +/** Compress preview bitmaps using the PNG format. + This compressor is lossless. +*/ +class PngCompression : public BitmapCompressor +{ + class PngReplacement; + +public: + virtual ~PngCompression() {} + virtual std::shared_ptr<BitmapReplacement> Compress(const BitmapEx& rBitmap) const override; + virtual BitmapEx Decompress(const BitmapReplacement& rBitmapData) const override; + virtual bool IsLossless() const override; +}; + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapFactory.cxx b/sd/source/ui/slidesorter/cache/SlsBitmapFactory.cxx new file mode 100644 index 000000000..a9182c2a2 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsBitmapFactory.cxx @@ -0,0 +1,71 @@ +/* -*- 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 "SlsBitmapFactory.hxx" + +#include <PreviewRenderer.hxx> +#include <sdpage.hxx> +#include <vcl/bitmapex.hxx> + +namespace sd::slidesorter::view { +class SlideSorterView; +class PageObjectViewObjectContact; +} + +namespace sd::slidesorter::cache { + +BitmapFactory::BitmapFactory() + : maRenderer(false) +{ +} + +BitmapFactory::~BitmapFactory() +{ +} + +BitmapEx BitmapFactory::CreateBitmap ( + const SdPage& rPage, + const Size& rPixelSize, + const bool bDoSuperSampling) +{ + Size aSize (rPixelSize); + if (bDoSuperSampling) + { + // Supersampling factor + int aSuperSamplingFactor = 2; + aSize.setWidth( aSize.Width() * aSuperSamplingFactor ); + aSize.setHeight( aSize.Height() * aSuperSamplingFactor ); + } + + BitmapEx aPreview (maRenderer.RenderPage ( + &rPage, + aSize, + true, + false).GetBitmapEx()); + if (bDoSuperSampling) + { + aPreview.Scale(rPixelSize, BmpScaleFlag::BestQuality); + } + + return aPreview; +} + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapFactory.hxx b/sd/source/ui/slidesorter/cache/SlsBitmapFactory.hxx new file mode 100644 index 000000000..c1733b825 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsBitmapFactory.hxx @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + +#pragma once + +#include <PreviewRenderer.hxx> + +class SdPage; +class Size; + +namespace sd::slidesorter::cache +{ +/** This factory class creates preview bitmaps for page objects. It is + merely an adapter for the PreviewRenderer. +*/ +class BitmapFactory +{ +public: + BitmapFactory(); + ~BitmapFactory(); + + BitmapEx CreateBitmap(const SdPage& rPage, const Size& rPixelSize, const bool bDoSuperSampling); + +private: + PreviewRenderer maRenderer; +}; + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsCacheCompactor.cxx b/sd/source/ui/slidesorter/cache/SlsCacheCompactor.cxx new file mode 100644 index 000000000..79ab9fab2 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsCacheCompactor.cxx @@ -0,0 +1,189 @@ +/* -*- 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 <memory> +#include "SlsCacheCompactor.hxx" + +#include "SlsBitmapCompressor.hxx" +#include "SlsBitmapCache.hxx" +#include "SlsCacheConfiguration.hxx" + +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <com/sun/star/uno/Any.hxx> + +using namespace ::com::sun::star::uno; + +namespace { + +/** This is a trivial implementation of the CacheCompactor interface class. + It ignores calls to RequestCompaction() and thus will never decrease the + total size of off-screen preview bitmaps. +*/ +class NoCacheCompaction + : public ::sd::slidesorter::cache::CacheCompactor +{ +public: + NoCacheCompaction ( + ::sd::slidesorter::cache::BitmapCache& rCache, + sal_Int32 nMaximalCacheSize) + : CacheCompactor(rCache, nMaximalCacheSize) + {} + + virtual void RequestCompaction() override { /* Ignored */ }; + +protected: + virtual void Run() override { /* Do nothing */ }; +}; + +/** This implementation of the CacheCompactor interface class uses one of + several bitmap compression algorithms to reduce the number of the bytes + of the off-screen previews in the bitmap cache. See the documentation + of CacheCompactor::Create() for more details on configuration properties + that control the choice of compression algorithm. +*/ +class CacheCompactionByCompression + : public ::sd::slidesorter::cache::CacheCompactor +{ +public: + CacheCompactionByCompression ( + ::sd::slidesorter::cache::BitmapCache& rCache, + sal_Int32 nMaximalCacheSize, + const std::shared_ptr< ::sd::slidesorter::cache::BitmapCompressor>& rpCompressor); + +protected: + virtual void Run() override; + +private: + std::shared_ptr< ::sd::slidesorter::cache::BitmapCompressor> mpCompressor; +}; + +} // end of anonymous namespace + +namespace sd::slidesorter::cache { + +::std::unique_ptr<CacheCompactor> CacheCompactor::Create ( + BitmapCache& rCache, + sal_Int32 nMaximalCacheSize) +{ + static const char sNone[] = "None"; + + std::shared_ptr<BitmapCompressor> pCompressor; + OUString sCompressionPolicy("PNGCompression"); + Any aCompressionPolicy (CacheConfiguration::Instance()->GetValue("CompressionPolicy")); + if (aCompressionPolicy.has<OUString>()) + aCompressionPolicy >>= sCompressionPolicy; + if (sCompressionPolicy == sNone) + pCompressor = std::make_shared<NoBitmapCompression>(); + else if (sCompressionPolicy == "Erase") + pCompressor = std::make_shared<CompressionByDeletion>(); + else if (sCompressionPolicy == "ResolutionReduction") + pCompressor = std::make_shared<ResolutionReduction>(); + else + pCompressor = std::make_shared<PngCompression>(); + + ::std::unique_ptr<CacheCompactor> pCompactor; + OUString sCompactionPolicy("Compress"); + Any aCompactionPolicy (CacheConfiguration::Instance()->GetValue("CompactionPolicy")); + if (aCompactionPolicy.has<OUString>()) + aCompactionPolicy >>= sCompactionPolicy; + if (sCompactionPolicy == sNone) + pCompactor.reset(new NoCacheCompaction(rCache,nMaximalCacheSize)); + else + pCompactor.reset(new CacheCompactionByCompression(rCache,nMaximalCacheSize,pCompressor)); + + return pCompactor; +} + +void CacheCompactor::RequestCompaction() +{ + if ( ! mbIsCompactionRunning && ! maCompactionTimer.IsActive()) + maCompactionTimer.Start(); +} + +CacheCompactor::CacheCompactor( + BitmapCache& rCache, + sal_Int32 nMaximalCacheSize) + : mrCache(rCache), + mnMaximalCacheSize(nMaximalCacheSize), + maCompactionTimer("sd CacheCompactor maCompactionTimer"), + mbIsCompactionRunning(false) +{ + maCompactionTimer.SetTimeout(100); + maCompactionTimer.SetInvokeHandler(LINK(this,CacheCompactor,CompactionCallback)); +} + +IMPL_LINK_NOARG(CacheCompactor, CompactionCallback, Timer *, void) +{ + mbIsCompactionRunning = true; + + try + { + Run(); + } + catch (const css::uno::RuntimeException&) + { + } + catch (const css::uno::Exception&) + { + } + + mbIsCompactionRunning = false; +} + +} // end of namespace ::sd::slidesorter::cache + +namespace { + +//===== CacheCompactionByCompression ========================================== + +CacheCompactionByCompression::CacheCompactionByCompression ( + ::sd::slidesorter::cache::BitmapCache& rCache, + sal_Int32 nMaximalCacheSize, + const std::shared_ptr< ::sd::slidesorter::cache::BitmapCompressor>& rpCompressor) + : CacheCompactor(rCache,nMaximalCacheSize), + mpCompressor(rpCompressor) +{ +} + +void CacheCompactionByCompression::Run() +{ + if (mrCache.GetSize() <= mnMaximalCacheSize) + return; + + SAL_INFO("sd.sls", __func__ << ": bitmap cache uses too much space: " << mrCache.GetSize() << " > " << mnMaximalCacheSize); + + ::sd::slidesorter::cache::BitmapCache::CacheIndex aIndex ( + mrCache.GetCacheIndex()); + for (const auto& rpIndex : aIndex) + { + if (rpIndex == nullptr) + continue; + + mrCache.Compress(rpIndex, mpCompressor); + if (mrCache.GetSize() < mnMaximalCacheSize) + break; + } + mrCache.ReCalculateTotalCacheSize(); + SAL_INFO("sd.sls", __func__ << ": there are now " << mrCache.GetSize() << " bytes occupied"); +} + +} // end of anonymous namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsCacheCompactor.hxx b/sd/source/ui/slidesorter/cache/SlsCacheCompactor.hxx new file mode 100644 index 000000000..d694ae1a1 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsCacheCompactor.hxx @@ -0,0 +1,87 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/types.h> +#include <vcl/timer.hxx> +#include <memory> + +namespace sd::slidesorter::cache { + +class BitmapCache; + +/** This is an interface class whose implementations are created via the + Create() factory method. +*/ +class CacheCompactor +{ +public: + virtual ~CacheCompactor() {}; + + /** Create a new instance of the CacheCompactor interface class. The + type of compaction algorithm used depends on values from the + configuration: the SlideSorter/PreviewCache/CompactionPolicy + property of the Impress.xcs file currently supports the values + "None" and "Compress". With the later the CompressionPolicy + property is evaluated which implementation of the BitmapCompress + interface class to use as bitmap compressor. + @param rCache + The bitmap cache on which to operate. + @param nMaximalCacheSize + The total number of bytes the off-screen bitmaps in the cache + may have. When the Run() method is (indirectly) called the + compactor tries to reduce that summed size of off-screen bitmaps + under this number. However, it is not guaranteed that this + works in all cases. + */ + static ::std::unique_ptr<CacheCompactor> Create ( + BitmapCache& rCache, + sal_Int32 nMaximalCacheSize); + + /** Request a compaction of the off-screen previews in the bitmap + cache. This calls via a timer the Run() method. + */ + virtual void RequestCompaction(); + +protected: + BitmapCache& mrCache; + sal_Int32 mnMaximalCacheSize; + + CacheCompactor( + BitmapCache& rCache, + sal_Int32 nMaximalCacheSize); + + /** This method actually tries to reduce the total number of bytes used + by the off-screen preview bitmaps. + */ + virtual void Run() = 0; + +private: + /** This timer is used to collect calls to RequestCompaction() and + eventually call Run(). + */ + Timer maCompactionTimer; + bool mbIsCompactionRunning; + DECL_LINK(CompactionCallback, Timer *, void); +}; + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.cxx b/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.cxx new file mode 100644 index 000000000..fd08c7627 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.cxx @@ -0,0 +1,144 @@ +/* -*- 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 "SlsCacheConfiguration.hxx" +#include <vcl/svapp.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace sd::slidesorter::cache { + +namespace +{ + typedef std::shared_ptr<CacheConfiguration> CacheConfigSharedPtr; + CacheConfigSharedPtr& theInstance() + { + static CacheConfigSharedPtr SINGLETON; + return SINGLETON; + } +} + +std::weak_ptr<CacheConfiguration> CacheConfiguration::mpWeakInstance; + +std::shared_ptr<CacheConfiguration> CacheConfiguration::Instance() +{ + SolarMutexGuard aSolarGuard; + CacheConfigSharedPtr &rInstancePtr = theInstance(); + if (!rInstancePtr) + { + // Maybe somebody else kept a previously created instance alive. + if ( ! mpWeakInstance.expired()) + rInstancePtr = std::shared_ptr<CacheConfiguration>(mpWeakInstance); + if (!rInstancePtr) + { + // We have to create a new instance. + rInstancePtr.reset(new CacheConfiguration()); + mpWeakInstance = rInstancePtr; + // Prepare to release this instance in the near future. + rInstancePtr->m_ReleaseTimer.SetInvokeHandler( + LINK(rInstancePtr.get(),CacheConfiguration,TimerCallback)); + rInstancePtr->m_ReleaseTimer.SetTimeout(5000 /* 5s */); + rInstancePtr->m_ReleaseTimer.Start(); + } + } + return rInstancePtr; +} + +CacheConfiguration::CacheConfiguration() + : m_ReleaseTimer("sd::CacheConfiguration maReleaseTimer") +{ + // Get the cache size from configuration. + try + { + // Obtain access to the configuration. + Reference<lang::XMultiServiceFactory> xProvider = + configuration::theDefaultProvider::get( ::comphelper::getProcessComponentContext() ); + + // Obtain access to Impress configuration. + Sequence<Any> aCreationArguments(comphelper::InitAnyPropertySequence( + { + {"nodepath", Any(OUString("/org.openoffice.Office.Impress/"))}, + {"depth", Any(sal_Int32(-1))} + })); + + Reference<XInterface> xRoot (xProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", + aCreationArguments)); + if ( ! xRoot.is()) + return; + Reference<container::XHierarchicalNameAccess> xHierarchy (xRoot, UNO_QUERY); + if ( ! xHierarchy.is()) + return; + + // Get the node for the slide sorter preview cache. + mxCacheNode.set( xHierarchy->getByHierarchicalName("MultiPaneGUI/SlideSorter/PreviewCache"), UNO_QUERY); + } + catch (RuntimeException &) + { + } + catch (Exception &) + { + } +} + +Any CacheConfiguration::GetValue (const OUString& rName) +{ + Any aResult; + + if (mxCacheNode != nullptr) + { + try + { + aResult = mxCacheNode->getByName(rName); + } + catch (Exception &) + { + } + } + + return aResult; +} + +IMPL_STATIC_LINK_NOARG(CacheConfiguration, TimerCallback, Timer *, void) +{ + CacheConfigSharedPtr &rInstancePtr = theInstance(); + // Release our reference to the instance. + rInstancePtr.reset(); + // note: if there are no other references to the instance, m_ReleaseTimer + // will be deleted now +} + +void CacheConfiguration::Shutdown() +{ + CacheConfigSharedPtr &rInstancePtr = theInstance(); + rInstancePtr.reset(); + assert(mpWeakInstance.expired()); // ensure m_ReleaseTimer is destroyed +} + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.hxx b/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.hxx new file mode 100644 index 000000000..d53bcd713 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Any.hxx> +#include <vcl/timer.hxx> +#include <memory> + +namespace com::sun::star::container +{ +class XNameAccess; +} + +namespace sd::slidesorter::cache +{ +/** A very simple and easy-to-use access to configuration entries regarding + the slide sorter cache. +*/ +class CacheConfiguration +{ +public: + /** Return an instance to this class. The reference is released after 5 + seconds. Subsequent calls to this function will create a new + instance. + */ + static std::shared_ptr<CacheConfiguration> Instance(); + + static void Shutdown(); + + /** Look up the specified value in + MultiPaneGUI/SlideSorter/PreviewCache. When the specified value + does not exist then an empty Any is returned. + */ + css::uno::Any GetValue(const OUString& rName); + +private: + /** When a caller holds a reference after we have released ours we use + this weak pointer to avoid creating a new instance. + */ + static std::weak_ptr<CacheConfiguration> mpWeakInstance; + Timer m_ReleaseTimer; + css::uno::Reference<css::container::XNameAccess> mxCacheNode; + + CacheConfiguration(); + + DECL_STATIC_LINK(CacheConfiguration, TimerCallback, Timer*, void); +}; + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsGenericPageCache.cxx b/sd/source/ui/slidesorter/cache/SlsGenericPageCache.cxx new file mode 100644 index 000000000..6275754fa --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsGenericPageCache.cxx @@ -0,0 +1,278 @@ +/* -*- 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 "SlsGenericPageCache.hxx" + +#include "SlsQueueProcessor.hxx" +#include "SlsRequestPriorityClass.hxx" +#include "SlsRequestFactory.hxx" +#include "SlsBitmapCache.hxx" +#include <cache/SlsPageCacheManager.hxx> +#include <tools/debug.hxx> + +namespace sd::slidesorter::cache { + +GenericPageCache::GenericPageCache ( + const Size& rPreviewSize, + const bool bDoSuperSampling, + const SharedCacheContext& rpCacheContext) + : maRequestQueue(rpCacheContext), + mpCacheContext(rpCacheContext), + maPreviewSize(rPreviewSize), + mbDoSuperSampling(bDoSuperSampling) +{ + // A large size may indicate an error of the caller. After all we + // are creating previews. + DBG_ASSERT (maPreviewSize.Width()<1000 && maPreviewSize.Height()<1000, + "GenericPageCache<>::GetPreviewBitmap(): bitmap requested with large width. " + "This may indicate an error."); +} + +GenericPageCache::~GenericPageCache() +{ + if (mpQueueProcessor != nullptr) + mpQueueProcessor->Stop(); + maRequestQueue.Clear(); + mpQueueProcessor.reset(); + + if (mpBitmapCache != nullptr) + PageCacheManager::Instance()->ReleaseCache(mpBitmapCache); + mpBitmapCache.reset(); +} + +void GenericPageCache::ProvideCacheAndProcessor() +{ + if (mpBitmapCache == nullptr) + mpBitmapCache = PageCacheManager::Instance()->GetCache( + mpCacheContext->GetModel(), + maPreviewSize); + + if (mpQueueProcessor == nullptr) + mpQueueProcessor.reset(new QueueProcessor( + maRequestQueue, + mpBitmapCache, + maPreviewSize, + mbDoSuperSampling, + mpCacheContext)); +} + +void GenericPageCache::ChangePreviewSize ( + const Size& rPreviewSize, + const bool bDoSuperSampling) +{ + if (rPreviewSize==maPreviewSize && bDoSuperSampling==mbDoSuperSampling) + return; + + // A large size may indicate an error of the caller. After all we + // are creating previews. + DBG_ASSERT (maPreviewSize.Width()<1000 && maPreviewSize.Height()<1000, + "GenericPageCache<>::GetPreviewBitmap(): bitmap requested with large width. " + "This may indicate an error."); + + if (mpBitmapCache != nullptr) + { + mpBitmapCache = PageCacheManager::Instance()->ChangeSize( + mpBitmapCache, maPreviewSize, rPreviewSize); + if (mpQueueProcessor != nullptr) + { + mpQueueProcessor->SetPreviewSize(rPreviewSize, bDoSuperSampling); + mpQueueProcessor->SetBitmapCache(mpBitmapCache); + } + } + maPreviewSize = rPreviewSize; + mbDoSuperSampling = bDoSuperSampling; +} + +BitmapEx GenericPageCache::GetPreviewBitmap ( + const CacheKey aKey, + const bool bResize) +{ + assert(aKey != nullptr); + + BitmapEx aPreview; + bool bMayBeUpToDate = true; + ProvideCacheAndProcessor(); + const SdrPage* pPage = mpCacheContext->GetPage(aKey); + if (mpBitmapCache->HasBitmap(pPage)) + { + aPreview = mpBitmapCache->GetBitmap(pPage); + const Size aBitmapSize (aPreview.GetSizePixel()); + if (aBitmapSize != maPreviewSize) + { + // Scale the bitmap to the desired size when that is possible, + // i.e. the bitmap is not empty. + if (bResize && !aBitmapSize.IsEmpty()) + { + aPreview.Scale(maPreviewSize); + } + bMayBeUpToDate = false; + } + else + bMayBeUpToDate = true; + } + else + bMayBeUpToDate = false; + + // Request the creation of a correctly sized preview bitmap. We do this + // even when the size of the bitmap in the cache is correct because its + // content may be not up-to-date anymore. + RequestPreviewBitmap(aKey, bMayBeUpToDate); + + return aPreview; +} + +BitmapEx GenericPageCache::GetMarkedPreviewBitmap ( + const CacheKey aKey) +{ + assert(aKey != nullptr); + + ProvideCacheAndProcessor(); + const SdrPage* pPage = mpCacheContext->GetPage(aKey); + BitmapEx aMarkedPreview (mpBitmapCache->GetMarkedBitmap(pPage)); + + return aMarkedPreview; +} + +void GenericPageCache::SetMarkedPreviewBitmap ( + const CacheKey aKey, + const BitmapEx& rMarkedBitmap) +{ + assert(aKey != nullptr); + + ProvideCacheAndProcessor(); + const SdrPage* pPage = mpCacheContext->GetPage(aKey); + mpBitmapCache->SetMarkedBitmap(pPage, rMarkedBitmap); +} + +void GenericPageCache::RequestPreviewBitmap ( + const CacheKey aKey, + const bool bMayBeUpToDate) +{ + assert(aKey != nullptr); + + const SdrPage* pPage = mpCacheContext->GetPage(aKey); + + ProvideCacheAndProcessor(); + + // Determine if the available bitmap is up to date. + bool bIsUpToDate = false; + if (bMayBeUpToDate) + bIsUpToDate = mpBitmapCache->BitmapIsUpToDate (pPage); + if (bIsUpToDate) + { + const BitmapEx aPreview (mpBitmapCache->GetBitmap(pPage)); + if (aPreview.IsEmpty() || aPreview.GetSizePixel()!=maPreviewSize) + bIsUpToDate = false; + } + + if ( bIsUpToDate) + return; + + // No, the bitmap is not up-to-date. Request a new one. + RequestPriorityClass ePriorityClass (NOT_VISIBLE); + if (mpCacheContext->IsVisible(aKey)) + { + if (mpBitmapCache->HasBitmap(pPage)) + ePriorityClass = VISIBLE_OUTDATED_PREVIEW; + else + ePriorityClass = VISIBLE_NO_PREVIEW; + } + maRequestQueue.AddRequest(aKey, ePriorityClass); + mpQueueProcessor->Start(ePriorityClass); +} + +bool GenericPageCache::InvalidatePreviewBitmap (const CacheKey aKey) +{ + // Invalidate the page in all caches that reference it, not just this one. + std::shared_ptr<cache::PageCacheManager> pCacheManager ( + cache::PageCacheManager::Instance()); + if (pCacheManager) + return pCacheManager->InvalidatePreviewBitmap( + mpCacheContext->GetModel(), + aKey); + else if (mpBitmapCache != nullptr) + return mpBitmapCache->InvalidateBitmap(mpCacheContext->GetPage(aKey)); + else + return false; +} + +void GenericPageCache::InvalidateCache () +{ + if (!mpBitmapCache) + return; + + // When the cache is being invalidated then it makes no sense to + // continue creating preview bitmaps. However, this may be + // re-started below. + mpQueueProcessor->Stop(); + maRequestQueue.Clear(); + + // Mark the previews in the cache as not being up-to-date anymore. + // Depending on the given bUpdateCache flag we start to create new + // preview bitmaps. + mpBitmapCache->InvalidateCache(); + RequestFactory()(maRequestQueue, mpCacheContext); +} + +void GenericPageCache::SetPreciousFlag ( + const CacheKey aKey, + const bool bIsPrecious) +{ + ProvideCacheAndProcessor(); + + // Change the request priority class according to the new precious flag. + if (bIsPrecious) + { + if (mpBitmapCache->HasBitmap(mpCacheContext->GetPage(aKey))) + maRequestQueue.ChangeClass(aKey,VISIBLE_OUTDATED_PREVIEW); + else + maRequestQueue.ChangeClass(aKey,VISIBLE_NO_PREVIEW); + } + else + { + if (mpBitmapCache->IsFull()) + { + // When the bitmap cache is full then requests for slides that + // are not visible are removed. + maRequestQueue.RemoveRequest(aKey); + } + else + maRequestQueue.ChangeClass(aKey,NOT_VISIBLE); + } + + mpBitmapCache->SetPrecious(mpCacheContext->GetPage(aKey), bIsPrecious); +} + +void GenericPageCache::Pause() +{ + ProvideCacheAndProcessor(); + if (mpQueueProcessor != nullptr) + mpQueueProcessor->Pause(); +} + +void GenericPageCache::Resume() +{ + ProvideCacheAndProcessor(); + if (mpQueueProcessor != nullptr) + mpQueueProcessor->Resume(); +} + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsGenericPageCache.hxx b/sd/source/ui/slidesorter/cache/SlsGenericPageCache.hxx new file mode 100644 index 000000000..900d40268 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsGenericPageCache.hxx @@ -0,0 +1,152 @@ +/* -*- 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 . + */ + +#pragma once + +#include "SlsRequestQueue.hxx" +#include <memory> + +#include <vcl/bitmapex.hxx> + +namespace sd::slidesorter::cache { + +class BitmapCache; +class QueueProcessor; + +/** This basically is the implementation class for the PageCache class. +*/ +class GenericPageCache +{ +public: + /** The page cache is created with a reference to the SlideSorter and + thus has access to both view and model. This allows the cache to + fill itself with requests for all pages or just the visible ones. + @param rPreviewSize + The size of the previews is expected in pixel values. + @param bDoSuperSampling + When <TRUE/> the previews are rendered larger and then scaled + down to the requested size to improve image quality. + */ + GenericPageCache ( + const Size& rPreviewSize, + const bool bDoSuperSampling, + const SharedCacheContext& rpCacheContext); + + ~GenericPageCache(); + + /** Change the size of the preview bitmaps. This may be caused by a + resize of the slide sorter window or a change of the number of + columns. + */ + void ChangePreviewSize ( + const Size& rPreviewSize, + const bool bDoSuperSampling); + + /** Request a preview bitmap for the specified page object in the + specified size. The returned bitmap may be a preview of the preview, + i.e. either a scaled (up or down) version of a previous preview (of + the wrong size) or an empty bitmap. In this case a request for the + generation of a new preview is created and inserted into the request + queue. When the preview is available the page shape will be told to + paint itself again. When it then calls this method again if + receives the correctly sized preview bitmap. + @param rRequestData + This data is used to determine the preview. + @param bResize + When <TRUE/> then when the available bitmap has not the + requested size, it is scaled before it is returned. When + <FALSE/> then the bitmap is returned in the wrong size and it is + the task of the caller to scale it. + @return + Returns a bitmap that is either empty, contains a scaled (up or + down) version or is the requested bitmap. + */ + BitmapEx GetPreviewBitmap ( + const CacheKey aKey, + const bool bResize); + BitmapEx GetMarkedPreviewBitmap ( + const CacheKey aKey); + void SetMarkedPreviewBitmap ( + const CacheKey aKey, + const BitmapEx& rMarkedBitmap); + + /** When the requested preview bitmap does not yet exist or is not + up-to-date then the rendering of one is scheduled. Otherwise this + method does nothing. + @param rRequestData + This data is used to determine the preview. + @param bMayBeUpToDate + This flag helps the method to determine whether an existing + preview that matches the request is up to date. If the caller + knows that it is not then by passing <FALSE/> he tells us that we + do not have to check the up-to-date flag a second time. If + unsure use <TRUE/>. + */ + void RequestPreviewBitmap ( + const CacheKey aKey, + const bool bMayBeUpToDate); + + /** Tell the cache to replace the bitmap associated with the given + request data with a new one that reflects recent changes in the + content of the page object. + @return + When the key is known then return <TRUE/>. + */ + bool InvalidatePreviewBitmap (const CacheKey aKey); + + /** Call this method when all preview bitmaps have to be generated anew. + This is the case when the size of the page objects on the screen has + changed or when the model has changed. + */ + void InvalidateCache (); + + /** With the precious flag you can control whether a bitmap can be + removed from the cache or reduced in size to make room for other + bitmaps or is so precious that it will not be touched. A typical + use is to set the precious flag for the visible pages. + */ + void SetPreciousFlag (const CacheKey aKey, const bool bIsPrecious); + + void Pause(); + void Resume(); + +private: + std::shared_ptr<BitmapCache> mpBitmapCache; + + RequestQueue maRequestQueue; + + std::unique_ptr<QueueProcessor> mpQueueProcessor; + + SharedCacheContext mpCacheContext; + + /** The current size of preview bitmaps. + */ + Size maPreviewSize; + + bool mbDoSuperSampling; + + /** Both bitmap cache and queue processor are created on demand by this + method. + */ + void ProvideCacheAndProcessor(); +}; + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsPageCache.cxx b/sd/source/ui/slidesorter/cache/SlsPageCache.cxx new file mode 100644 index 000000000..82b1b8ae4 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsPageCache.cxx @@ -0,0 +1,109 @@ +/* -*- 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 <tools/gen.hxx> +#include "SlsGenericPageCache.hxx" +#include <cache/SlsPageCache.hxx> + +using namespace ::com::sun::star; + +namespace sd::slidesorter::cache { + +//===== PageCache ============================================================= + +PageCache::PageCache ( + const Size& rPreviewSize, + const bool bDoSuperSampling, + const SharedCacheContext& rpCacheContext) + : mpImplementation( + new GenericPageCache( + rPreviewSize, + bDoSuperSampling, + rpCacheContext)) +{ +} + +PageCache::~PageCache() +{ +} + +void PageCache::ChangeSize ( + const Size& rPreviewSize, + const bool bDoSuperSampling) +{ + mpImplementation->ChangePreviewSize(rPreviewSize, bDoSuperSampling); +} + +BitmapEx PageCache::GetPreviewBitmap ( + const CacheKey aKey, + const bool bResize) +{ + return mpImplementation->GetPreviewBitmap(aKey, bResize); +} + +BitmapEx PageCache::GetMarkedPreviewBitmap ( + const CacheKey aKey) +{ + return mpImplementation->GetMarkedPreviewBitmap(aKey); +} + +void PageCache::SetMarkedPreviewBitmap ( + const CacheKey aKey, + const BitmapEx& rMarkedBitmap) +{ + mpImplementation->SetMarkedPreviewBitmap(aKey, rMarkedBitmap); +} + +void PageCache::RequestPreviewBitmap (const CacheKey aKey) +{ + return mpImplementation->RequestPreviewBitmap(aKey, true); +} + +void PageCache::InvalidatePreviewBitmap ( + const CacheKey aKey) +{ + if (mpImplementation->InvalidatePreviewBitmap(aKey)) + RequestPreviewBitmap(aKey); +} + +void PageCache::InvalidateCache() +{ + mpImplementation->InvalidateCache(); +} + +void PageCache::SetPreciousFlag ( + const CacheKey aKey, + const bool bIsPrecious) +{ + mpImplementation->SetPreciousFlag(aKey, bIsPrecious); +} + +void PageCache::Pause() +{ + mpImplementation->Pause(); +} + +void PageCache::Resume() +{ + mpImplementation->Resume(); +} + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsPageCacheManager.cxx b/sd/source/ui/slidesorter/cache/SlsPageCacheManager.cxx new file mode 100644 index 000000000..45afd93c9 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsPageCacheManager.cxx @@ -0,0 +1,420 @@ +/* -*- 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 <cache/SlsPageCacheManager.hxx> + +#include "SlsBitmapCache.hxx" + +#include <deque> +#include <map> +#include <memory> +#include <unordered_map> + +namespace { + +/** Collection of data that is stored for all active preview caches. +*/ +class CacheDescriptor +{ +public: + ::sd::slidesorter::cache::PageCacheManager::DocumentKey mpDocument; + Size maPreviewSize; + + CacheDescriptor( + ::sd::slidesorter::cache::PageCacheManager::DocumentKey const & pDocument, + const Size& rPreviewSize) + :mpDocument(pDocument),maPreviewSize(rPreviewSize) + {} + /// Test for equality with respect to all members. + class Equal {public: bool operator() ( + const CacheDescriptor& rDescriptor1, const CacheDescriptor& rDescriptor2) const { + return rDescriptor1.mpDocument==rDescriptor2.mpDocument + && rDescriptor1.maPreviewSize==rDescriptor2.maPreviewSize; + } }; + /// Hash function that takes all members into account. + class Hash {public: size_t operator() (const CacheDescriptor& rDescriptor) const { + return reinterpret_cast<size_t>(rDescriptor.mpDocument.get()) + rDescriptor.maPreviewSize.Width(); + } }; +}; + +/** Collection of data that is stored for the inactive, recently used + caches. +*/ +class RecentlyUsedCacheDescriptor +{ +public: + Size maPreviewSize; + std::shared_ptr< ::sd::slidesorter::cache::BitmapCache> mpCache; + + RecentlyUsedCacheDescriptor( + const Size& rPreviewSize, + const std::shared_ptr< ::sd::slidesorter::cache::BitmapCache>& rpCache) + :maPreviewSize(rPreviewSize),mpCache(rpCache) + {} +}; + +/** The list of recently used caches is organized as queue. When elements + are added the list is shortened to the maximally allowed number of + elements by removing the least recently used elements. +*/ +typedef ::std::deque<RecentlyUsedCacheDescriptor> RecentlyUsedQueue; + +/** Compare the caches by preview size. Those that match the given size + come first, then, regardless of the given size, the largest ones before + the smaller ones. +*/ +class BestFittingCacheComparer +{ +public: + explicit BestFittingCacheComparer (const Size& rPreferredSize) + : maPreferredSize(rPreferredSize) + {} + bool operator()(const ::sd::slidesorter::cache::PageCacheManager::BestFittingPageCaches::value_type& rElement1, + const ::sd::slidesorter::cache::PageCacheManager::BestFittingPageCaches::value_type& rElement2) + { + if (rElement2.first == maPreferredSize) + return false; + else if (rElement1.first == maPreferredSize) + return true; + else + return (rElement1.first.Width()*rElement1.first.Height() + > rElement2.first.Width()*rElement2.first.Height()); + } + +private: + Size maPreferredSize; +}; + +} // end of anonymous namespace + +namespace sd::slidesorter::cache { + +/** Container for the active caches. +*/ +class PageCacheManager::PageCacheContainer + : public std::unordered_map<CacheDescriptor, + std::shared_ptr<BitmapCache>, + CacheDescriptor::Hash, + CacheDescriptor::Equal> +{ +public: + PageCacheContainer() {} + + /** Compare entries in the cache container with respect to the cache + address only. + */ + class CompareWithCache { public: + explicit CompareWithCache(const std::shared_ptr<BitmapCache>& rpCache) + : mpCache(rpCache) {} + bool operator () (const PageCacheContainer::value_type& rValue) const + { return rValue.second == mpCache; } + private: + std::shared_ptr<BitmapCache> mpCache; + }; +}; + +/** The recently used caches are stored in one queue for each document. +*/ +class PageCacheManager::RecentlyUsedPageCaches +{ +public: + typedef DocumentKey key_type; + typedef RecentlyUsedQueue mapped_type; + typedef std::map<key_type,mapped_type>::iterator iterator; +private: + std::map<key_type,mapped_type> maMap; +public: + RecentlyUsedPageCaches () {}; + + iterator end() { return maMap.end(); } + void clear() { maMap.clear(); } + iterator find(const key_type& key) { return maMap.find(key); } + template<class... Args> + std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); } +}; + +class PageCacheManager::Deleter +{ +public: + void operator() (PageCacheManager* pObject) { delete pObject; } +}; + +//===== PageCacheManager ==================================================== + +std::weak_ptr<PageCacheManager> PageCacheManager::mpInstance; + +std::shared_ptr<PageCacheManager> PageCacheManager::Instance() +{ + std::shared_ptr<PageCacheManager> pInstance; + + ::osl::MutexGuard aGuard (::osl::Mutex::getGlobalMutex()); + + pInstance = mpInstance.lock(); + if (pInstance == nullptr) + { + pInstance = std::shared_ptr<PageCacheManager>( + new PageCacheManager(), + PageCacheManager::Deleter()); + mpInstance = pInstance; + } + + return pInstance; +} + +PageCacheManager::PageCacheManager() + : mpPageCaches(new PageCacheContainer()), + mpRecentlyUsedPageCaches(new RecentlyUsedPageCaches()) +{ +} + +PageCacheManager::~PageCacheManager() +{ +} + +std::shared_ptr<BitmapCache> PageCacheManager::GetCache ( + const DocumentKey& pDocument, + const Size& rPreviewSize) +{ + std::shared_ptr<BitmapCache> pResult; + + // Look for the cache in the list of active caches. + CacheDescriptor aKey (pDocument, rPreviewSize); + PageCacheContainer::iterator iCache (mpPageCaches->find(aKey)); + if (iCache != mpPageCaches->end()) + pResult = iCache->second; + + // Look for the cache in the list of recently used caches. + if (pResult == nullptr) + pResult = GetRecentlyUsedCache(pDocument, rPreviewSize); + + // Create the cache when no suitable one does exist. + if (pResult == nullptr) + pResult = std::make_shared<BitmapCache>(); + + // The cache may be newly created and thus empty or is old and may + // contain previews that are not up-to-date. Recycle previews from + // other caches to fill in the holes. + Recycle(pResult, pDocument,rPreviewSize); + + // Put the new (or old) cache into the container. + mpPageCaches->emplace(aKey, pResult); + + return pResult; +} + +void PageCacheManager::Recycle ( + const std::shared_ptr<BitmapCache>& rpCache, + const DocumentKey& pDocument, + const Size& rPreviewSize) +{ + BestFittingPageCaches aCaches; + + // Add bitmap caches from active caches. + for (auto& rActiveCache : *mpPageCaches) + { + if (rActiveCache.first.mpDocument == pDocument) + aCaches.emplace_back( + rActiveCache.first.maPreviewSize, rActiveCache.second); + } + + // Add bitmap caches from recently used caches. + RecentlyUsedPageCaches::iterator iQueue (mpRecentlyUsedPageCaches->find(pDocument)); + if (iQueue != mpRecentlyUsedPageCaches->end()) + { + for (const auto& rRecentCache : iQueue->second) + aCaches.emplace_back( + rRecentCache.maPreviewSize, rRecentCache.mpCache); + } + + ::std::sort(aCaches.begin(), aCaches.end(), BestFittingCacheComparer(rPreviewSize)); + + for (const auto& rBestCache : aCaches) + { + rpCache->Recycle(*rBestCache.second); + } +} + +void PageCacheManager::ReleaseCache (const std::shared_ptr<BitmapCache>& rpCache) +{ + PageCacheContainer::iterator iCache (::std::find_if( + mpPageCaches->begin(), + mpPageCaches->end(), + PageCacheContainer::CompareWithCache(rpCache))); + + if (iCache != mpPageCaches->end()) + { + assert(iCache->second == rpCache); + + PutRecentlyUsedCache(iCache->first.mpDocument,iCache->first.maPreviewSize,rpCache); + + mpPageCaches->erase(iCache); + } +} + +std::shared_ptr<BitmapCache> PageCacheManager::ChangeSize ( + const std::shared_ptr<BitmapCache>& rpCache, + const Size&, + const Size& rNewPreviewSize) +{ + std::shared_ptr<BitmapCache> pResult; + + if (rpCache != nullptr) + { + // Look up the given cache in the list of active caches. + PageCacheContainer::iterator iCacheToChange (::std::find_if( + mpPageCaches->begin(), + mpPageCaches->end(), + PageCacheContainer::CompareWithCache(rpCache))); + if (iCacheToChange != mpPageCaches->end()) + { + assert(iCacheToChange->second == rpCache); + + // Now, we can change the preview size of the existing one by + // removing the cache from the list and re-insert it with the + // updated size. + const ::sd::slidesorter::cache::PageCacheManager::DocumentKey aKey ( + iCacheToChange->first.mpDocument); + mpPageCaches->erase(iCacheToChange); + mpPageCaches->emplace( + CacheDescriptor(aKey,rNewPreviewSize), + rpCache); + + pResult = rpCache; + } + else + { + assert(iCacheToChange != mpPageCaches->end()); + } + } + + return pResult; +} + +bool PageCacheManager::InvalidatePreviewBitmap ( + const DocumentKey& pDocument, + const SdrPage* pKey) +{ + bool bHasChanged (false); + + if (pDocument!=nullptr) + { + // Iterate over all caches that are currently in use and invalidate + // the previews in those that belong to the document. + for (auto& rCache : *mpPageCaches) + if (rCache.first.mpDocument == pDocument) + bHasChanged |= rCache.second->InvalidateBitmap(pKey); + + // Invalidate the previews in the recently used caches belonging to + // the given document. + RecentlyUsedPageCaches::iterator iQueue (mpRecentlyUsedPageCaches->find(pDocument)); + if (iQueue != mpRecentlyUsedPageCaches->end()) + { + for (const auto& rCache2 : iQueue->second) + bHasChanged |= rCache2.mpCache->InvalidateBitmap(pKey); + } + } + + return bHasChanged; +} + +void PageCacheManager::InvalidateAllPreviewBitmaps (const DocumentKey& pDocument) +{ + if (pDocument == nullptr) + return; + + // Iterate over all caches that are currently in use and invalidate the + // previews in those that belong to the document. + for (auto& rCache : *mpPageCaches) + if (rCache.first.mpDocument == pDocument) + rCache.second->InvalidateCache(); + + // Invalidate the previews in the recently used caches belonging to the + // given document. + RecentlyUsedPageCaches::iterator iQueue (mpRecentlyUsedPageCaches->find(pDocument)); + if (iQueue != mpRecentlyUsedPageCaches->end()) + { + for (const auto& rCache2 : iQueue->second) + rCache2.mpCache->InvalidateCache(); + } +} + +void PageCacheManager::InvalidateAllCaches() +{ + // Iterate over all caches that are currently in use and invalidate + // them. + for (auto& rCache : *mpPageCaches) + rCache.second->InvalidateCache(); + + // Remove all recently used caches, there is not much sense in storing + // invalidated and unused caches. + mpRecentlyUsedPageCaches->clear(); +} + +void PageCacheManager::ReleasePreviewBitmap (const SdrPage* pPage) +{ + for (auto& rCache : *mpPageCaches) + rCache.second->ReleaseBitmap(pPage); +} + +std::shared_ptr<BitmapCache> PageCacheManager::GetRecentlyUsedCache ( + const DocumentKey& pDocument, + const Size& rPreviewSize) +{ + std::shared_ptr<BitmapCache> pCache; + + // Look for the cache in the list of recently used caches. + RecentlyUsedPageCaches::iterator iQueue (mpRecentlyUsedPageCaches->find(pDocument)); + if (iQueue != mpRecentlyUsedPageCaches->end()) + { + RecentlyUsedQueue::iterator iCache = std::find_if(iQueue->second.begin(), iQueue->second.end(), + [&rPreviewSize](const RecentlyUsedCacheDescriptor& rCache) { return rCache.maPreviewSize == rPreviewSize; }); + if (iCache != iQueue->second.end()) + { + pCache = iCache->mpCache; + iQueue->second.erase(iCache); + } + } + + return pCache; +} + +void PageCacheManager::PutRecentlyUsedCache( + DocumentKey const & pDocument, + const Size& rPreviewSize, + const std::shared_ptr<BitmapCache>& rpCache) +{ + // Look up the list of recently used caches for the given document. + RecentlyUsedPageCaches::iterator iQueue (mpRecentlyUsedPageCaches->find(pDocument)); + if (iQueue == mpRecentlyUsedPageCaches->end()) + iQueue = mpRecentlyUsedPageCaches->emplace( + pDocument, RecentlyUsedQueue() + ).first; + + if (iQueue != mpRecentlyUsedPageCaches->end()) + { + iQueue->second.push_front(RecentlyUsedCacheDescriptor(rPreviewSize,rpCache)); + // Shorten the list of recently used caches to the allowed maximal length. + while (iQueue->second.size() > mnMaximalRecentlyCacheCount) + iQueue->second.pop_back(); + } +} + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsQueueProcessor.cxx b/sd/source/ui/slidesorter/cache/SlsQueueProcessor.cxx new file mode 100644 index 000000000..077c48709 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsQueueProcessor.cxx @@ -0,0 +1,176 @@ +/* -*- 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 "SlsQueueProcessor.hxx" +#include "SlsRequestQueue.hxx" +#include "SlsBitmapCache.hxx" + +#include <sdpage.hxx> +#include <comphelper/profilezone.hxx> +#include <tools/diagnose_ex.h> + +namespace sd::slidesorter::cache { + +//===== QueueProcessor ====================================================== + +QueueProcessor::QueueProcessor ( + RequestQueue& rQueue, + const std::shared_ptr<BitmapCache>& rpCache, + const Size& rPreviewSize, + const bool bDoSuperSampling, + const SharedCacheContext& rpCacheContext) + : maTimer("sd::QueueProcessor maTimer"), + maPreviewSize(rPreviewSize), + mbDoSuperSampling(bDoSuperSampling), + mpCacheContext(rpCacheContext), + mrQueue(rQueue), + mpCache(rpCache), + mbIsPaused(false) +{ + maTimer.SetInvokeHandler (LINK(this,QueueProcessor,ProcessRequestHdl)); + maTimer.SetTimeout (10); +} + +QueueProcessor::~QueueProcessor() +{ +} + +void QueueProcessor::Start (int nPriorityClass) +{ + if (mbIsPaused) + return; + if ( ! maTimer.IsActive()) + { + if (nPriorityClass == 0) + maTimer.SetTimeout (10); + else + maTimer.SetTimeout (100); + maTimer.Start(); + } +} + +void QueueProcessor::Stop() +{ + if (maTimer.IsActive()) + maTimer.Stop(); +} + +void QueueProcessor::Pause() +{ + mbIsPaused = true; +} + +void QueueProcessor::Resume() +{ + mbIsPaused = false; + if ( ! mrQueue.IsEmpty()) + Start(mrQueue.GetFrontPriorityClass()); +} + +void QueueProcessor::SetPreviewSize ( + const Size& rPreviewSize, + const bool bDoSuperSampling) +{ + maPreviewSize = rPreviewSize; + mbDoSuperSampling = bDoSuperSampling; +} + +IMPL_LINK_NOARG(QueueProcessor, ProcessRequestHdl, Timer *, void) +{ + ProcessRequests(); +} + +void QueueProcessor::ProcessRequests() +{ + assert(mpCacheContext); + + // Never process more than one request at a time in order to prevent the + // lock up of the edit view. + if ( ! mrQueue.IsEmpty() + && ! mbIsPaused + && mpCacheContext->IsIdle()) + { + CacheKey aKey = nullptr; + RequestPriorityClass ePriorityClass (NOT_VISIBLE); + { + ::osl::MutexGuard aGuard (mrQueue.GetMutex()); + + if ( ! mrQueue.IsEmpty()) + { + // Get the request with the highest priority from the queue. + ePriorityClass = mrQueue.GetFrontPriorityClass(); + aKey = mrQueue.GetFront(); + mrQueue.PopFront(); + } + } + + if (aKey != nullptr) + ProcessOneRequest(aKey, ePriorityClass); + } + + // Schedule the processing of the next element(s). + { + ::osl::MutexGuard aGuard (mrQueue.GetMutex()); + if ( ! mrQueue.IsEmpty()) + Start(mrQueue.GetFrontPriorityClass()); + else + { + comphelper::ProfileZone aZone("QueueProcessor finished processing all elements"); + } + } +} + +void QueueProcessor::ProcessOneRequest ( + CacheKey aKey, + const RequestPriorityClass ePriorityClass) +{ + try + { + std::scoped_lock aGuard (maMutex); + + // Create a new preview bitmap and store it in the cache. + if (mpCache != nullptr && mpCacheContext) + { + const SdPage* pSdPage = dynamic_cast<const SdPage*>(mpCacheContext->GetPage(aKey)); + if (pSdPage != nullptr) + { + const BitmapEx aPreview ( + maBitmapFactory.CreateBitmap(*pSdPage, maPreviewSize, mbDoSuperSampling)); + mpCache->SetBitmap (pSdPage, aPreview, ePriorityClass!=NOT_VISIBLE); + + // Initiate a repaint of the new preview. + mpCacheContext->NotifyPreviewCreation(aKey); + } + } + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION( "sd", "QueueProcessor"); + } +} + +void QueueProcessor::SetBitmapCache ( + const std::shared_ptr<BitmapCache>& rpCache) +{ + mpCache = rpCache; +} + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsQueueProcessor.hxx b/sd/source/ui/slidesorter/cache/SlsQueueProcessor.hxx new file mode 100644 index 000000000..0035bcbce --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsQueueProcessor.hxx @@ -0,0 +1,98 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cache/SlsCacheContext.hxx> +#include "SlsRequestPriorityClass.hxx" +#include "SlsBitmapFactory.hxx" + +#include <vcl/timer.hxx> +#include <mutex> + +namespace sd::slidesorter::cache { + +class BitmapCache; +class RequestQueue; + +/** This queue processor is timer based, i.e. when an entry is added to the + queue and the processor is started with Start() in the base class a + timer is started that eventually calls ProcessRequest(). This is + repeated until the queue is empty or Stop() is called. +*/ +class QueueProcessor final +{ +public: + QueueProcessor ( + RequestQueue& rQueue, + const std::shared_ptr<BitmapCache>& rpCache, + const Size& rPreviewSize, + const bool bDoSuperSampling, + const SharedCacheContext& rpCacheContext); + ~QueueProcessor(); + + /** Start the processor. This implementation is timer based and waits + a defined amount of time that depends on the given argument before + the next entry in the queue is processed. + @param nPriorityClass + A priority class of 0 tells the processor that a high priority + request is waiting in the queue. The time to wait is thus + shorter then that for a low priority request (denoted by a value + of 1.) When the timer is already running it is not modified. + */ + void Start (int nPriorityClass); + void Stop(); + void Pause(); + void Resume(); + + void SetPreviewSize ( + const Size& rSize, + const bool bDoSuperSampling); + + /** Use this method when the page cache is (maybe) using a different + BitmapCache. This is usually necessary after calling + PageCacheManager::ChangeSize(). + */ + void SetBitmapCache (const std::shared_ptr<BitmapCache>& rpCache); + +private: + /** This mutex is used to guard the queue processor. Be careful not to + mix its use with that of the solar mutex. + */ + std::mutex maMutex; + + Timer maTimer; + DECL_LINK(ProcessRequestHdl, Timer *, void); + Size maPreviewSize; + bool mbDoSuperSampling; + SharedCacheContext mpCacheContext; + RequestQueue& mrQueue; + std::shared_ptr<BitmapCache> mpCache; + BitmapFactory maBitmapFactory; + bool mbIsPaused; + + void ProcessRequests(); + void ProcessOneRequest ( + CacheKey aKey, + const RequestPriorityClass ePriorityClass); +}; + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsRequestFactory.cxx b/sd/source/ui/slidesorter/cache/SlsRequestFactory.cxx new file mode 100644 index 000000000..6fc6cabc9 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsRequestFactory.cxx @@ -0,0 +1,50 @@ +/* -*- 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 "SlsRequestFactory.hxx" +#include "SlsRequestQueue.hxx" + +namespace sd::slidesorter::cache { + +void RequestFactory::operator()( + RequestQueue& rRequestQueue, + const SharedCacheContext& rpCacheContext) +{ + std::shared_ptr<std::vector<CacheKey> > aKeys; + + // Add the requests for the visible pages. + aKeys = rpCacheContext->GetEntryList(true); + if (aKeys != nullptr) + { + for (const auto& rKey : *aKeys) + rRequestQueue.AddRequest(rKey, VISIBLE_NO_PREVIEW); + } + + // Add the requests for the non-visible pages. + aKeys = rpCacheContext->GetEntryList(false); + if (aKeys != nullptr) + { + for (const auto& rKey : *aKeys) + rRequestQueue.AddRequest(rKey, NOT_VISIBLE); + } +} + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsRequestFactory.hxx b/sd/source/ui/slidesorter/cache/SlsRequestFactory.hxx new file mode 100644 index 000000000..3f4207725 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsRequestFactory.hxx @@ -0,0 +1,36 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cache/SlsCacheContext.hxx> + +namespace sd::slidesorter::cache +{ +class RequestQueue; + +class RequestFactory +{ +public: + void operator()(RequestQueue& rRequestQueue, const SharedCacheContext& rpCacheContext); +}; + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsRequestPriorityClass.hxx b/sd/source/ui/slidesorter/cache/SlsRequestPriorityClass.hxx new file mode 100644 index 000000000..2c84ecbcf --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsRequestPriorityClass.hxx @@ -0,0 +1,44 @@ +/* -*- 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 . + */ + +#pragma once + +namespace sd::slidesorter::cache +{ +/** Each request for a preview creation has a priority. This enum defines + the available priorities. The special values MIN_CLASS and MAX_CLASS + are/can be used for validation and have to be kept up-to-date. +*/ +enum RequestPriorityClass +{ + MIN_CLASS = 0, + + // The slide is visible. A preview does not yet exist. + VISIBLE_NO_PREVIEW = MIN_CLASS, + // The slide is visible. A preview exists but is not up-to-date anymore. + VISIBLE_OUTDATED_PREVIEW, + // The slide is not visible. + NOT_VISIBLE, + + MAX_CLASS = NOT_VISIBLE +}; + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsRequestQueue.cxx b/sd/source/ui/slidesorter/cache/SlsRequestQueue.cxx new file mode 100644 index 000000000..931c1a8f6 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsRequestQueue.cxx @@ -0,0 +1,275 @@ +/* -*- 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 "SlsRequestQueue.hxx" + +#include <sal/log.hxx> + +#include <svx/svdpage.hxx> + +#include <set> + +namespace sd::slidesorter::cache { + +namespace { + +/** This class extends the actual request data with additional information + that is used by the priority queues. +*/ +class Request +{ +public: + Request ( + CacheKey aKey, sal_Int32 nPriority, RequestPriorityClass eClass) + : maKey(aKey), mnPriorityInClass(nPriority), meClass(eClass) + {} + /** Sort requests according to priority classes and then to priorities. + */ + class Comparator { public: + bool operator() (const Request& rRequest1, const Request& rRequest2) + const + { + if (rRequest1.meClass == rRequest2.meClass) + { + if (rRequest1.mnPriorityInClass == rRequest2.mnPriorityInClass) + { + return rRequest1.maKey < rRequest2.maKey; + } + return rRequest1.mnPriorityInClass > rRequest2.mnPriorityInClass; + } + return rRequest1.meClass < rRequest2.meClass; + } + }; + /** Request data is compared arbitrarily by their addresses in memory. + This just establishes an order so that the STL containers are happy. + The order is not semantically interpreted. + */ + class DataComparator + { + public: + explicit DataComparator (const CacheKey aKey) + : maKey(aKey) + { + } + bool operator() (const Request& rRequest) const + { + return maKey == rRequest.maKey; + } + private: + const CacheKey maKey; + }; + + CacheKey maKey; + sal_Int32 mnPriorityInClass; + RequestPriorityClass meClass; +}; + +} + +class RequestQueue::Container + : public ::std::set< + Request, + Request::Comparator> +{ +}; + +//===== GenericRequestQueue ================================================= + +RequestQueue::RequestQueue (const SharedCacheContext& rpCacheContext) + : mpRequestQueue(new Container), + mpCacheContext(rpCacheContext), + mnMinimumPriority(0), + mnMaximumPriority(1) +{ +} + +RequestQueue::~RequestQueue() +{ + Clear(); +} + +void RequestQueue::AddRequest ( + CacheKey aKey, + RequestPriorityClass eRequestClass) +{ + ::osl::MutexGuard aGuard (maMutex); + + assert(eRequestClass>=MIN_CLASS && eRequestClass<=MAX_CLASS); + + // If the request is already a member of the queue then remove it so + // that the following insertion will use the new prioritization. +#if OSL_DEBUG_LEVEL >=2 + bool bRemoved = +#endif + RemoveRequest(aKey); + + // The priority of the request inside its priority class is defined by + // the page number. This ensures a strict top-to-bottom, left-to-right + // order. + sal_Int32 nPriority (mpCacheContext->GetPriority(aKey)); + Request aRequest (aKey, nPriority, eRequestClass); + + std::pair<Container::iterator,bool> ret = mpRequestQueue->insert(aRequest); + bool bInserted = ret.second; + + if (bInserted) + { + SdrPage *pPage = const_cast<SdrPage*>(aRequest.maKey); + pPage->AddPageUser(*this); + } + +#if OSL_DEBUG_LEVEL >=2 + SAL_INFO("sd.sls", __func__ << ": " << (bRemoved?"replaced":"added") + << " request for page " << ((aKey->GetPageNum()-1)/2) + << " with priority class " << static_cast<int>(eRequestClass)); +#endif +} + +void RequestQueue::PageInDestruction(const SdrPage& rPage) +{ + //remove any requests pending for this page which is going away now + RemoveRequest(&rPage); +} + +#if OSL_DEBUG_LEVEL >=2 +bool +#else +void +#endif +RequestQueue::RemoveRequest( + CacheKey aKey) +{ + ::osl::MutexGuard aGuard (maMutex); +#if OSL_DEBUG_LEVEL >=2 + bool bIsRemoved = false; +#endif + while(true) + { + Container::const_iterator aRequestIterator = ::std::find_if ( + mpRequestQueue->begin(), + mpRequestQueue->end(), + Request::DataComparator(aKey)); + if (aRequestIterator != mpRequestQueue->end()) + { + if (aRequestIterator->mnPriorityInClass == mnMinimumPriority+1) + mnMinimumPriority++; + else if (aRequestIterator->mnPriorityInClass == mnMaximumPriority-1) + mnMaximumPriority--; + + SdrPage *pPage = const_cast<SdrPage*>(aRequestIterator->maKey); + pPage->RemovePageUser(*this); + mpRequestQueue->erase(aRequestIterator); +#if OSL_DEBUG_LEVEL >=2 + bIsRemoved = true; +#endif + } + else + break; + } +#if OSL_DEBUG_LEVEL >=2 + return bIsRemoved; +#endif + +} + +void RequestQueue::ChangeClass ( + CacheKey aKey, + RequestPriorityClass eNewRequestClass) +{ + ::osl::MutexGuard aGuard (maMutex); + + assert(eNewRequestClass>=MIN_CLASS && eNewRequestClass<=MAX_CLASS); + + Container::const_iterator iRequest ( + ::std::find_if ( + mpRequestQueue->begin(), + mpRequestQueue->end(), + Request::DataComparator(aKey))); + if (iRequest!=mpRequestQueue->end() && iRequest->meClass!=eNewRequestClass) + { + AddRequest(aKey, eNewRequestClass); + } +} + +CacheKey RequestQueue::GetFront() +{ + ::osl::MutexGuard aGuard (maMutex); + + if (mpRequestQueue->empty()) + throw css::uno::RuntimeException("RequestQueue::GetFront(): queue is empty", + nullptr); + + return mpRequestQueue->begin()->maKey; +} + +RequestPriorityClass RequestQueue::GetFrontPriorityClass() +{ + ::osl::MutexGuard aGuard (maMutex); + + if (mpRequestQueue->empty()) + throw css::uno::RuntimeException("RequestQueue::GetFrontPriorityClass(): queue is empty", + nullptr); + + return mpRequestQueue->begin()->meClass; +} + +void RequestQueue::PopFront() +{ + ::osl::MutexGuard aGuard (maMutex); + + if ( mpRequestQueue->empty()) + return; + + Container::const_iterator aIter(mpRequestQueue->begin()); + SdrPage *pPage = const_cast<SdrPage*>(aIter->maKey); + pPage->RemovePageUser(*this); + mpRequestQueue->erase(aIter); + + // Reset the priority counter if possible. + if (mpRequestQueue->empty()) + { + mnMinimumPriority = 0; + mnMaximumPriority = 1; + } +} + +bool RequestQueue::IsEmpty() +{ + ::osl::MutexGuard aGuard (maMutex); + return mpRequestQueue->empty(); +} + +void RequestQueue::Clear() +{ + ::osl::MutexGuard aGuard (maMutex); + + for (const auto& rItem : *mpRequestQueue) + { + SdrPage *pPage = const_cast<SdrPage*>(rItem.maKey); + pPage->RemovePageUser(*this); + } + + mpRequestQueue->clear(); + mnMinimumPriority = 0; + mnMaximumPriority = 1; +} + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/cache/SlsRequestQueue.hxx b/sd/source/ui/slidesorter/cache/SlsRequestQueue.hxx new file mode 100644 index 000000000..618ba5801 --- /dev/null +++ b/sd/source/ui/slidesorter/cache/SlsRequestQueue.hxx @@ -0,0 +1,122 @@ +/* -*- 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 . + */ + +#pragma once + +#include "SlsRequestPriorityClass.hxx" +#include <cache/SlsCacheContext.hxx> +#include <osl/mutex.hxx> +#include <svx/sdrpageuser.hxx> + +#include <memory> + +namespace sd::slidesorter::cache +{ +/** The request queue stores requests that are described by the Request + sorted according to priority class and then priority. +*/ +class RequestQueue : public sdr::PageUser +{ +public: + explicit RequestQueue(const SharedCacheContext& rpCacheContext); + virtual ~RequestQueue(); + + /** Insert a request with highest or lowest priority in its priority + class. When the request is already present then it is first + removed. This effect is then a re-prioritization. + @param aKey + The request. + @param eRequestClass + The priority class in which to insert the request with highest + or lowest priority. + @param bInsertWithHighestPriority + When this flag is <TRUE/> the request is inserted with highest + priority in its class. When <FALSE/> the request is inserted + with lowest priority. + */ + void AddRequest(CacheKey aKey, RequestPriorityClass eRequestClass); + + /** Remove the specified request from the queue. + @param aKey + It is OK when the specified request is not a member of the + queue. + */ +#if OSL_DEBUG_LEVEL >= 2 + bool +#else + void +#endif + RemoveRequest(CacheKey aKey); + + /** Change the priority class of the specified request. + */ + void ChangeClass(CacheKey aKey, RequestPriorityClass eNewRequestClass); + + /** Get the request with the highest priority int the highest priority class. + */ + CacheKey GetFront(); + + // For debugging. + RequestPriorityClass GetFrontPriorityClass(); + + /** Really a synonym for RemoveRequest(GetFront()); + */ + void PopFront(); + + /** Returns <TRUE/> when there is no element in the queue. + */ + bool IsEmpty(); + + /** Remove all requests from the queue. This resets the minimum and + maximum priorities to their default values. + */ + void Clear(); + + /** Return the mutex that guards the access to the priority queue. + */ + ::osl::Mutex& GetMutex() { return maMutex; } + + /** Ensure we don't hand out a page deleted before anyone got a + chance to process it + */ + virtual void PageInDestruction(const SdrPage& rPage) override; + +private: + ::osl::Mutex maMutex; + class Container; + std::unique_ptr<Container> mpRequestQueue; + SharedCacheContext mpCacheContext; + + /** A lower bound of the lowest priority of all elements in the queues. + The start value is 0. It is assigned and then decreased every time + when an element is inserted or marked as the request with lowest + priority. + */ + int mnMinimumPriority; + /** An upper bound of the highest priority of all elements in the queues. + The start value is 1. It is assigned and then increased every time + when an element is inserted or marked as the request with highest + priority. + */ + int mnMaximumPriority; +}; + +} // end of namespace ::sd::slidesorter::cache + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |