desktop/inc/lib/init.hxx                       |    3 +
 desktop/source/lib/init.cxx                    |   12 +++++-
 include/sfx2/viewsh.hxx                        |    2 +
 sfx2/source/view/viewsh.cxx                    |    5 ++
 sw/inc/view.hxx                                |    1 
 sw/inc/viewsh.hxx                              |    1 
 sw/qa/core/txtnode/txtnode.cxx                 |    1 
 sw/qa/extras/tiledrendering/tiledrendering.cxx |    9 ++++
 sw/source/core/inc/viewimp.hxx                 |    5 ++
 sw/source/core/view/viewimp.cxx                |   15 +++++++
 sw/source/core/view/viewsh.cxx                 |   49 +++++++++++++++++++++++++
 sw/source/uibase/uiview/view.cxx               |    7 +++
 12 files changed, 109 insertions(+), 1 deletion(-)

New commits:
commit cb60edbc2c0725f0309a5b934f41e999a1fe0c5f
Author:     Luboš Luňák <[email protected]>
AuthorDate: Tue Oct 12 14:24:44 2021 +0200
Commit:     Luboš Luňák <[email protected]>
CommitDate: Fri Oct 15 13:40:30 2021 +0200

    delay, collect and compress LOK invalidations for Writer views
    
    Due the to way views are updated on any document change, invalidations
    are at least O(n^2), and since LOK may use a number of views and
    for each view the entire document is considered to be the view area,
    this can lead to a huge number of invalidations that are mostly
    the same repeated rectangles.
    
    Change-Id: I63682958d2fc388344641dcd19fa1d2b39054b51
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/123617
    Tested-by: Jenkins
    Reviewed-by: Luboš Luňák <[email protected]>

diff --git a/desktop/inc/lib/init.hxx b/desktop/inc/lib/init.hxx
index b31b52eabaa0..9f15377ba0ec 100644
--- a/desktop/inc/lib/init.hxx
+++ b/desktop/inc/lib/init.hxx
@@ -98,6 +98,8 @@ namespace desktop {
         void addViewStates(int viewId);
         void removeViewStates(int viewId);
 
+        void setViewId( int viewId ) { m_viewId = viewId; }
+
         // SfxLockCallbackInterface
         virtual void libreOfficeKitViewCallback(int nType, const char* 
pPayload) override;
         virtual void libreOfficeKitViewCallback(int nType, const char* 
pPayload, int nViewId) override;
@@ -177,6 +179,7 @@ namespace desktop {
         std::map<int, std::string> m_states;
         std::unordered_map<int, std::unordered_map<int, std::string>> 
m_viewStates;
         LibreOfficeKitDocument* m_pDocument;
+        int m_viewId = -1; // view id of the associated SfxViewShell
         LibreOfficeKitCallback m_pCallback;
         void *m_pData;
         int m_nDisableCallbacks;
diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx
index 521fe2a18be9..535cf9808d4d 100644
--- a/desktop/source/lib/init.cxx
+++ b/desktop/source/lib/init.cxx
@@ -2039,6 +2039,14 @@ void CallbackFlushHandler::Invoke()
     if (!m_pCallback)
         return;
 
+    // Get any pending invalidate tile events. This will call our callbacks,
+    // so it must be done before taking the mutex.
+    if(SfxViewShell* viewShell = SfxViewShell::GetFirst( false,
+        [this](const SfxViewShell* shell) { return 
shell->GetViewShellId().get() == m_viewId; } ))
+    {
+        viewShell->flushPendingLOKInvalidateTiles();
+    }
+
     std::scoped_lock<std::mutex> lock(m_mutex);
 
     SAL_INFO("lok", "Flushing " << m_queue1.size() << " elements.");
@@ -3423,7 +3431,7 @@ static void doc_paintPartTile(LibreOfficeKitDocument* 
pThis,
                 {
                     if (pViewShell->getPart() == nPart)
                     {
-                        nViewId = 
static_cast<sal_Int32>(pViewShell->GetViewShellId());
+                        nViewId = pViewShell->GetViewShellId().get();
                         doc_setView(pThis, nViewId);
                         break;
                     }
@@ -3556,6 +3564,7 @@ static void doc_registerCallback(LibreOfficeKitDocument* 
pThis,
 
         if (SfxViewShell* pViewShell = SfxViewShell::Current())
         {
+            
pDocument->mpCallbackFlushHandlers[nView]->setViewId(pViewShell->GetViewShellId().get());
             
pViewShell->setLibreOfficeKitViewCallback(pDocument->mpCallbackFlushHandlers[nView].get());
         }
     }
@@ -3564,6 +3573,7 @@ static void doc_registerCallback(LibreOfficeKitDocument* 
pThis,
         if (SfxViewShell* pViewShell = SfxViewShell::Current())
         {
             pViewShell->setLibreOfficeKitViewCallback(nullptr);
+            pDocument->mpCallbackFlushHandlers[nView]->setViewId(-1);
         }
     }
 }
diff --git a/include/sfx2/viewsh.hxx b/include/sfx2/viewsh.hxx
index 44201db40e41..c972d67c7f13 100644
--- a/include/sfx2/viewsh.hxx
+++ b/include/sfx2/viewsh.hxx
@@ -340,6 +340,8 @@ public:
     virtual void libreOfficeKitViewCallback(int nType, const char* pPayload) 
const override;
     virtual void libreOfficeKitViewCallback(int nType, const char* pPayload, 
int nViewId) const override;
     virtual void libreOfficeKitViewInvalidateTilesCallback(const 
tools::Rectangle* pRect, int nPart) const override;
+    // Performs any pending calls to 
libreOfficeKitViewInvalidateTilesCallback() as necessary.
+    virtual void flushPendingLOKInvalidateTiles();
 
     /// Set if we are doing tiled searching.
     void setTiledSearching(bool bTiledSearching);
diff --git a/sfx2/source/view/viewsh.cxx b/sfx2/source/view/viewsh.cxx
index 9f5ff0e1ff85..4b12dae1c0ee 100644
--- a/sfx2/source/view/viewsh.cxx
+++ b/sfx2/source/view/viewsh.cxx
@@ -1527,6 +1527,11 @@ void SfxViewShell::afterCallbackRegistered()
 {
 }
 
+void SfxViewShell::flushPendingLOKInvalidateTiles()
+{
+    // SfxViewShell itself does not delay any tile invalidations.
+}
+
 vcl::Window* SfxViewShell::GetEditWindowForActiveOLEObj() const
 {
     vcl::Window* pEditWin = nullptr;
diff --git a/sw/inc/view.hxx b/sw/inc/view.hxx
index a69e0c2e1483..7a5423732a6e 100644
--- a/sw/inc/view.hxx
+++ b/sw/inc/view.hxx
@@ -692,6 +692,7 @@ public:
     void SetOldDrwCat(const OUString& sStr);
 
     virtual tools::Rectangle getLOKVisibleArea() const override;
+    virtual void flushPendingLOKInvalidateTiles() override;
 };
 
 inline tools::Long SwView::GetXScroll() const
diff --git a/sw/inc/viewsh.hxx b/sw/inc/viewsh.hxx
index 6c0b40302a72..620412dfc227 100644
--- a/sw/inc/viewsh.hxx
+++ b/sw/inc/viewsh.hxx
@@ -211,6 +211,7 @@ public:
     bool AddPaintRect( const SwRect &rRect );
 
     void InvalidateWindows( const SwRect &rRect );
+    void FlushPendingLOKInvalidateTiles();
 
     /// Invalidates complete Layout (ApplyViewOption).
     void Reformat();
diff --git a/sw/qa/core/txtnode/txtnode.cxx b/sw/qa/core/txtnode/txtnode.cxx
index 43d8b2104b9d..49472c25cecd 100644
--- a/sw/qa/core/txtnode/txtnode.cxx
+++ b/sw/qa/core/txtnode/txtnode.cxx
@@ -125,6 +125,7 @@ CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, 
testTitleFieldInvalidate)
 
     // When typing to the document:
     pWrtShell->Insert("x");
+    pWrtShell->GetSfxViewShell()->flushPendingLOKInvalidateTiles();
 
     // Then make sure that only the text frame at the cursor is invalidated:
     pDoc->getIDocumentStatistics().GetUpdatedDocStat(/*bCompleteAsync=*/true, 
/*bFields=*/false);
diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx 
b/sw/qa/extras/tiledrendering/tiledrendering.cxx
index a96cac16c652..8f637753d1d4 100644
--- a/sw/qa/extras/tiledrendering/tiledrendering.cxx
+++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx
@@ -438,6 +438,7 @@ void SwTiledRenderingTest::testRegisterCallback()
     pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(this);
     // Insert a character at the beginning of the document.
     pWrtShell->Insert("x");
+    pWrtShell->GetSfxViewShell()->flushPendingLOKInvalidateTiles();
 
     // Check that the top left 256x256px tile would be invalidated.
     CPPUNIT_ASSERT(!m_aInvalidation.IsEmpty());
@@ -854,6 +855,11 @@ namespace {
             mpViewShell->setLibreOfficeKitViewCallback(nullptr);
         }
 
+        void flushPendingLOKInvalidateTiles()
+        {
+            mpViewShell->flushPendingLOKInvalidateTiles();
+        }
+
         virtual void libreOfficeKitViewCallback(int nType, const char* 
pPayload, int /*nViewId*/) override
         {
             libreOfficeKitViewCallback(nType, pPayload); // the view id is 
also included in payload
@@ -1037,6 +1043,8 @@ void SwTiledRenderingTest::testMissingInvalidation()
     pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE);
     pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE);
     Scheduler::ProcessEventsToIdle();
+    aView1.flushPendingLOKInvalidateTiles();
+    aView2.flushPendingLOKInvalidateTiles();
     CPPUNIT_ASSERT(aView1.m_bTilesInvalidated);
     CPPUNIT_ASSERT(aView2.m_bTilesInvalidated);
 }
@@ -1715,6 +1723,7 @@ void SwTiledRenderingTest::testCommentEndTextEdit()
     pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN);
     pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN);
     Scheduler::ProcessEventsToIdle();
+    aView1.flushPendingLOKInvalidateTiles();
     CPPUNIT_ASSERT(aView1.m_bTilesInvalidated);
 }
 
diff --git a/sw/source/core/inc/viewimp.hxx b/sw/source/core/inc/viewimp.hxx
index 06e8968d9483..fdd0ed330592 100644
--- a/sw/source/core/inc/viewimp.hxx
+++ b/sw/source/core/inc/viewimp.hxx
@@ -66,6 +66,8 @@ class SwViewShellImp
     SwPageFrame     *m_pFirstVisiblePage; // Always points to the first 
visible Page
     std::unique_ptr<SwRegionRects> m_pPaintRegion; // Collector of Paintrects 
from the LayAction
 
+    std::vector<SwRect> m_pendingLOKInvalidations;
+
     SwLayAction   *m_pLayAction;      // Is set if an Action object exists
                                  // Is registered by the SwLayAction ctor and 
deregistered by the dtor
     SwLayIdle     *m_pIdleAct;     // The same as SwLayAction for SwLayIdle
@@ -151,6 +153,9 @@ public:
     const SwRegionRects* GetPaintRegion() { return m_pPaintRegion.get(); }
     void DeletePaintRegion() { m_pPaintRegion.reset(); }
 
+    void AddPendingLOKInvalidation( const SwRect& rRect );
+    std::vector<SwRect> TakePendingLOKInvalidations();
+
     /// New Interface for StarView Drawing
     bool  HasDrawView()             const { return nullptr != m_pDrawView; }
           SwDrawView* GetDrawView()       { return m_pDrawView.get(); }
diff --git a/sw/source/core/view/viewimp.cxx b/sw/source/core/view/viewimp.cxx
index abb2ef6b174a..f1b2c2531897 100644
--- a/sw/source/core/view/viewimp.cxx
+++ b/sw/source/core/view/viewimp.cxx
@@ -158,6 +158,21 @@ bool SwViewShellImp::AddPaintRect( const SwRect &rRect )
     return false;
 }
 
+void SwViewShellImp::AddPendingLOKInvalidation( const SwRect& rRect )
+{
+    // These are often repeated, so check first for duplicates.
+    std::vector<SwRect>& l = m_pendingLOKInvalidations;
+    if( std::find( l.begin(), l.end(), rRect ) == l.end())
+        l.push_back( rRect );
+}
+
+std::vector<SwRect> SwViewShellImp::TakePendingLOKInvalidations()
+{
+    std::vector<SwRect> ret;
+    std::swap(ret, m_pendingLOKInvalidations);
+    return ret;
+}
+
 void SwViewShellImp::CheckWaitCursor()
 {
     if ( m_pLayAction )
diff --git a/sw/source/core/view/viewsh.cxx b/sw/source/core/view/viewsh.cxx
index c1adf1af8337..f97a61c2387a 100644
--- a/sw/source/core/view/viewsh.cxx
+++ b/sw/source/core/view/viewsh.cxx
@@ -551,6 +551,24 @@ void SwViewShell::InvalidateWindows( const SwRect &rRect )
     if ( Imp()->IsCalcLayoutProgress() )
         return;
 
+    if(comphelper::LibreOfficeKit::isActive())
+    {
+        // First collect all invalidations and perform them only later,
+        // otherwise the number of Invalidate() calls would be at least
+        // O(n^2) if not worse. The problem is that if any change in a document
+        // is made, SwEditShell::EndAllAction() is called, which calls 
EndAction()
+        // for every view. And every view does it own handling of paint 
rectangles,
+        // and then calls InvalidateWindows() based on that. On then this code
+        // would call Invalidate() for all views for each rectangle.
+        // So collect the rectangles, avoid duplicates (which there usually 
will
+        // be many because of the repetitions), 
FlushPendingLOKInvalidateTiles()
+        // will collect all rectangles from all related views, compress them
+        // and only with those relatively few rectangle it'd call Invalidate()
+        // for all views.
+        Imp()->AddPendingLOKInvalidation(rRect);
+        return;
+    }
+
     for(SwViewShell& rSh : GetRingContainer())
     {
         if ( rSh.GetWin() )
@@ -565,6 +583,37 @@ void SwViewShell::InvalidateWindows( const SwRect &rRect )
     }
 }
 
+void SwViewShell::FlushPendingLOKInvalidateTiles()
+{
+    assert(comphelper::LibreOfficeKit::isActive());
+    SwRegionRects rects;
+    for(SwViewShell& rSh : GetRingContainer())
+    {
+        std::vector<SwRect> tmpRects = 
rSh.Imp()->TakePendingLOKInvalidations();
+        rects.insert( rects.end(), tmpRects.begin(), tmpRects.end());
+    }
+    rects.Compress( SwRegionRects::CompressFuzzy );
+    if(rects.empty())
+        return;
+    // This is basically the loop from SwViewShell::InvalidateWindows().
+    for(SwViewShell& rSh : GetRingContainer())
+    {
+        if ( rSh.GetWin() )
+        {
+            if ( rSh.IsPreview() )
+            {
+                for( const SwRect& rect : rects )
+                    ::RepaintPagePreview( &rSh, rect );
+            }
+            else
+            {
+                for( const SwRect& rect : rects )
+                    rSh.GetWin()->Invalidate( rect.SVRect() );
+            }
+        }
+    }
+}
+
 const SwRect& SwViewShell::VisArea() const
 {
     // when using the tiled rendering, consider the entire document as our
diff --git a/sw/source/uibase/uiview/view.cxx b/sw/source/uibase/uiview/view.cxx
index a8d230f33793..9a4b83a28566 100644
--- a/sw/source/uibase/uiview/view.cxx
+++ b/sw/source/uibase/uiview/view.cxx
@@ -1905,6 +1905,13 @@ tools::Rectangle SwView::getLOKVisibleArea() const
         return tools::Rectangle();
 }
 
+void SwView::flushPendingLOKInvalidateTiles()
+{
+    SwWrtShell* pSh = GetWrtShellPtr();
+    assert(pSh);
+    pSh->FlushPendingLOKInvalidateTiles();
+}
+
 OUString SwView::GetDataSourceName() const
 {
     uno::Reference<lang::XMultiServiceFactory> 
xFactory(GetDocShell()->GetModel(), uno::UNO_QUERY);

Reply via email to