include/o3tl/lru_map.hxx         |   14 ++++-
 sc/inc/conditio.hxx              |   13 ++++
 sc/source/core/data/conditio.cxx |  102 ++++++++++++++++++++++++++++++++-------
 3 files changed, 110 insertions(+), 19 deletions(-)

New commits:
commit 7ca98e3865df26e4418d1429d5006b39881c1cdc
Author:     Caolán McNamara <[email protected]>
AuthorDate: Tue Dec 2 08:21:17 2025 +0000
Commit:     Caolán McNamara <[email protected]>
CommitDate: Fri Dec 12 10:41:29 2025 +0100

    optimize relative conditional formatting effort
    
    cache FormulaCells that the relative conditional formatting needs
    to avoid regenerating them constantly on scrolling.
    
    Use something size bounded (lru_map) to avoid potentially creating a
    massive spreadsheet-sized list of formula cells.
    
    Register the cache with the usual cache machinery so they can be
    dropped on memory pressure/idle.
    
    Change-Id: I38dca513791347558225432af0ceda94a7b2742e
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/195317
    Tested-by: Jenkins
    Reviewed-by: Caolán McNamara <[email protected]>

diff --git a/include/o3tl/lru_map.hxx b/include/o3tl/lru_map.hxx
index 65268ea85b4d..64428d568f64 100644
--- a/include/o3tl/lru_map.hxx
+++ b/include/o3tl/lru_map.hxx
@@ -205,7 +205,7 @@ public:
         checkLRUMaxSize();
     }
 
-    void insert(key_value_pair_t& rPair)
+    list_const_iterator_t insert(key_value_pair_t& rPair)
     {
         map_iterator_t i = mLruMap.find(rPair.first);
 
@@ -230,9 +230,11 @@ public:
             mLruList.splice(mLruList.begin(), mLruList, i->second);
             checkLRUItemUpdate();
         }
+
+        return mLruList.cbegin();
     }
 
-    void insert(key_value_pair_t&& rPair)
+    list_const_iterator_t insert(key_value_pair_t&& rPair)
     {
         map_iterator_t i = mLruMap.find(rPair.first);
 
@@ -256,6 +258,8 @@ public:
             mLruList.splice(mLruList.begin(), mLruList, i->second);
             checkLRUItemUpdate();
         }
+
+        return mLruList.cbegin();
     }
 
     list_const_iterator_t find(const Key& key)
@@ -301,6 +305,12 @@ public:
         return mLruMap.size();
     }
 
+    size_t empty() const
+    {
+        assert(mLruMap.empty() == mLruList.empty());
+        return mLruMap.empty();
+    }
+
     // size_t total_size() const; - only if custom ValueSize
 
     void clear()
diff --git a/sc/inc/conditio.hxx b/sc/inc/conditio.hxx
index f38b81cc999e..35252a185067 100644
--- a/sc/inc/conditio.hxx
+++ b/sc/inc/conditio.hxx
@@ -34,6 +34,8 @@
 #include <rtl/math.hxx>
 #include <tools/date.hxx>
 #include <tools/link.hxx>
+#include <o3tl/lru_map.hxx>
+#include <vcl/dropcache.hxx>
 
 #include <optional>
 #include <map>
@@ -301,7 +303,7 @@ public:
     }
 };
 
-class SAL_DLLPUBLIC_RTTI ScConditionEntry : public ScFormatEntry
+class SAL_DLLPUBLIC_RTTI ScConditionEntry : public ScFormatEntry, public 
CacheOwner
 {
                                         // stored data:
     ScConditionMode     eOp;
@@ -323,6 +325,9 @@ class SAL_DLLPUBLIC_RTTI ScConditionEntry : public 
ScFormatEntry
     OUString              aSrcString;     // formula source position as text 
during XML import
     std::unique_ptr<ScFormulaCell>  pFCell1;
     std::unique_ptr<ScFormulaCell>  pFCell2;
+    typedef o3tl::lru_map<ScAddress, std::unique_ptr<ScFormulaCell>> 
RelRefCells;
+    std::unique_ptr<RelRefCells> xRelRefCells1;
+    std::unique_ptr<RelRefCells> xRelRefCells2;
     bool                bRelRef1;
     bool                bRelRef2;
     bool                bFirstRun;
@@ -346,6 +351,8 @@ class SAL_DLLPUBLIC_RTTI ScConditionEntry : public 
ScFormatEntry
     bool    IsValidStr( const OUString& rArg, const ScAddress& rPos ) const;
     void    StartListening();
 
+    static std::unique_ptr<RelRefCells> makeRelRefCells();
+
 public:
             ScConditionEntry( ScConditionMode eOper,
                                 const OUString& rExpr1, const OUString& rExpr2,
@@ -401,6 +408,10 @@ public:
     virtual void UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt ) 
override;
     virtual void UpdateMoveTab( sc::RefUpdateMoveTabContext& rCxt ) override;
 
+    virtual bool dropCaches() override;
+    virtual void dumpState(rtl::OStringBuffer& rState) override;
+    virtual OUString getCacheName() const override;
+
     bool            MarkUsedExternalReferences() const;
 
     virtual Type GetType() const override { return eConditionType; }
diff --git a/sc/source/core/data/conditio.cxx b/sc/source/core/data/conditio.cxx
index 76afe0e67a30..58f2b09030ee 100644
--- a/sc/source/core/data/conditio.cxx
+++ b/sc/source/core/data/conditio.cxx
@@ -450,7 +450,9 @@ void ScConditionEntry::SetCaseSensitive(bool bSet)
 void ScConditionEntry::CompileAll()
 {
     pFCell1.reset();
+    xRelRefCells1.reset();
     pFCell2.reset();
+    xRelRefCells2.reset();
 }
 
 void ScConditionEntry::CompileXML()
@@ -545,7 +547,11 @@ void ScConditionEntry::UpdateReference( 
sc::RefUpdateContext& rCxt )
         }
 
         if (aRes.mbReferenceModified || bChangedPos)
-            pFCell1.reset();       // is created again in IsValid
+        {
+            // is created again in IsValid
+            pFCell1.reset();
+            xRelRefCells1.reset();
+        }
     }
 
     if (pFormula2)
@@ -564,7 +570,11 @@ void ScConditionEntry::UpdateReference( 
sc::RefUpdateContext& rCxt )
         }
 
         if (aRes.mbReferenceModified || bChangedPos)
-            pFCell2.reset();       // is created again in IsValid
+        {
+            // is created again in IsValid
+            pFCell2.reset();
+            xRelRefCells2.reset();
+        }
     }
 
     StartListening();
@@ -576,12 +586,14 @@ void ScConditionEntry::UpdateInsertTab( 
sc::RefUpdateInsertTabContext& rCxt )
     {
         pFormula1->AdjustReferenceOnInsertedTab(rCxt, aSrcPos);
         pFCell1.reset();
+        xRelRefCells1.reset();
     }
 
     if (pFormula2)
     {
         pFormula2->AdjustReferenceOnInsertedTab(rCxt, aSrcPos);
         pFCell2.reset();
+        xRelRefCells2.reset();
     }
 
     ScRangeUpdater::UpdateInsertTab(aSrcPos, rCxt);
@@ -593,12 +605,14 @@ void ScConditionEntry::UpdateDeleteTab( 
sc::RefUpdateDeleteTabContext& rCxt )
     {
         pFormula1->AdjustReferenceOnDeletedTab(rCxt, aSrcPos);
         pFCell1.reset();
+        xRelRefCells1.reset();
     }
 
     if (pFormula2)
     {
         pFormula2->AdjustReferenceOnDeletedTab(rCxt, aSrcPos);
         pFCell2.reset();
+        xRelRefCells2.reset();
     }
 
     ScRangeUpdater::UpdateDeleteTab(aSrcPos, rCxt);
@@ -615,6 +629,7 @@ void 
ScConditionEntry::UpdateMoveTab(sc::RefUpdateMoveTabContext& rCxt)
         if (aRes.mbValueChanged)
             aResFinal.mnTab = aRes.mnTab;
         pFCell1.reset();
+        xRelRefCells1.reset();
     }
 
     if (pFormula2)
@@ -623,6 +638,7 @@ void 
ScConditionEntry::UpdateMoveTab(sc::RefUpdateMoveTabContext& rCxt)
         if (aRes.mbValueChanged)
             aResFinal.mnTab = aRes.mnTab;
         pFCell2.reset();
+        xRelRefCells2.reset();
     }
 
     if (aResFinal.mnTab != aSrcPos.Tab())
@@ -669,6 +685,12 @@ bool ScConditionEntry::IsEqual( const ScFormatEntry& 
rOther, bool bIgnoreSrcPos
     return true;
 }
 
+//static
+std::unique_ptr<ScConditionEntry::RelRefCells> 
ScConditionEntry::makeRelRefCells()
+{
+    return std::make_unique<RelRefCells>(0x9fff);
+}
+
 void ScConditionEntry::Interpret( const ScAddress& rPos )
 {
     // Create formula cells
@@ -679,16 +701,25 @@ void ScConditionEntry::Interpret( const ScAddress& rPos )
     // Evaluate formulas
     bool bDirty = false; // 1 and 2 separate?
 
-    std::optional<ScFormulaCell> oTemp;
     ScFormulaCell* pEff1 = pFCell1.get();
     if ( bRelRef1 )
     {
-        if (pFormula1)
-            oTemp.emplace(mrDoc, rPos, *pFormula1);
+        if (!xRelRefCells1)
+            xRelRefCells1 = makeRelRefCells();
+        auto iFind = xRelRefCells1->find(rPos);
+        if (iFind != xRelRefCells1->end())
+            pEff1 = iFind->second.get();
         else
-            oTemp.emplace(mrDoc, rPos);
-        pEff1 = &*oTemp;
-        pEff1->SetFreeFlying(true);
+        {
+            std::unique_ptr<ScFormulaCell> xCell;
+            if (pFormula1)
+                xCell = std::make_unique<ScFormulaCell>(mrDoc, rPos, 
*pFormula1);
+            else
+                xCell = std::make_unique<ScFormulaCell>(mrDoc, rPos);
+            xCell->SetFreeFlying(true);
+            xCell->StartListeningTo(mrDoc);
+            pEff1 = xRelRefCells1->insert(std::make_pair(rPos, 
std::move(xCell)))->second.get();
+        }
     }
     if ( pEff1 )
     {
@@ -711,17 +742,26 @@ void ScConditionEntry::Interpret( const ScAddress& rPos )
             }
         }
     }
-    oTemp.reset();
 
     ScFormulaCell* pEff2 = pFCell2.get(); //@ 1!=2
     if ( bRelRef2 )
     {
-        if (pFormula2)
-            oTemp.emplace(mrDoc, rPos, *pFormula2);
+        if (!xRelRefCells2)
+            xRelRefCells2 = makeRelRefCells();
+        auto iFind = xRelRefCells2->find(rPos);
+        if (iFind != xRelRefCells2->end())
+            pEff2 = iFind->second.get();
         else
-            oTemp.emplace(mrDoc, rPos);
-        pEff2 = &*oTemp;
-        pEff2->SetFreeFlying(true);
+        {
+            std::unique_ptr<ScFormulaCell> xCell;
+            if (pFormula2)
+                xCell = std::make_unique<ScFormulaCell>(mrDoc, rPos, 
*pFormula2);
+            else
+                xCell = std::make_unique<ScFormulaCell>(mrDoc, rPos);
+            xCell->SetFreeFlying(true);
+            xCell->StartListeningTo(mrDoc);
+            pEff2 = xRelRefCells2->insert(std::make_pair(rPos, 
std::move(xCell)))->second.get();
+        }
     }
     if ( pEff2 )
     {
@@ -743,7 +783,6 @@ void ScConditionEntry::Interpret( const ScAddress& rPos )
             }
         }
     }
-    oTemp.reset();
 
     // If IsRunning, the last values remain
     if (bDirty && !bFirstRun)
@@ -1558,16 +1597,47 @@ ScFormatEntry* ScCondFormatEntry::Clone( ScDocument& 
rDoc ) const
 
 void ScConditionEntry::CalcAll()
 {
-    if (pFCell1 || pFCell2)
+    if (pFCell1 || pFCell2 || xRelRefCells1 || !xRelRefCells2)
     {
         if (pFCell1)
             pFCell1->SetDirty();
+        if (xRelRefCells1)
+        {
+            for (auto& rEntry : *xRelRefCells1)
+                rEntry.second->SetDirty();
+        }
         if (pFCell2)
             pFCell2->SetDirty();
+        if (xRelRefCells2)
+        {
+            for (auto& rEntry : *xRelRefCells2)
+                rEntry.second->SetDirty();
+        }
         pCondFormat->DoRepaint();
     }
 }
 
+OUString ScConditionEntry::getCacheName() const
+{
+    return "ScConditionEntry";
+}
+
+bool ScConditionEntry::dropCaches()
+{
+    xRelRefCells1.reset();
+    xRelRefCells2.reset();
+    return true;
+}
+
+void ScConditionEntry::dumpState(rtl::OStringBuffer& rState)
+{
+    rState.append("
ScConditionEntry:");
+    rState.append("
         rel ref cells1: ");
+    rState.append(xRelRefCells1 ? 
static_cast<sal_Int32>(xRelRefCells1->size()) : 0);
+    rState.append("
         rel ref cells2: ");
+    rState.append(xRelRefCells2 ? 
static_cast<sal_Int32>(xRelRefCells2->size()) : 0);
+}
+
 ScCondDateFormatEntry::ScCondDateFormatEntry( ScDocument& rDoc )
     : ScFormatEntry( rDoc )
     , meType(condformat::TODAY)

Reply via email to