Skip to content

Commit 8a17b7f

Browse files
grandinjNoel Grandin
authored and
Noel Grandin
committed
tdf#131595 Improve drawinglayer flushing mechanism.
Which dramatically speeds up switching between sheets in the referenced document. The problem here is that we have a timer for each buffered primitive. And we have a lot of primitives here. And the TimerManager does not scale well to lots and lots of timer, because it uses a linked list. So this change modifies the flushing mechanism, trading off some precision for some speed. Change-Id: I66a744f06af9b08d4e9b797d11db8d22f4060789 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182876 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
1 parent f382c1a commit 8a17b7f

16 files changed

+270
-214
lines changed

drawinglayer/Library_drawinglayer.mk

-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ $(eval $(call gb_Library_add_exception_objects,drawinglayer,\
109109
drawinglayer/source/primitive2d/bitmapprimitive2d \
110110
drawinglayer/source/primitive2d/BitmapAlphaPrimitive2D \
111111
drawinglayer/source/primitive2d/borderlineprimitive2d \
112-
drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D \
113112
drawinglayer/source/primitive2d/controlprimitive2d \
114113
drawinglayer/source/primitive2d/cropprimitive2d \
115114
drawinglayer/source/primitive2d/discretebitmapprimitive2d \
@@ -125,7 +124,6 @@ $(eval $(call gb_Library_add_exception_objects,drawinglayer,\
125124
drawinglayer/source/primitive2d/graphicprimitivehelper2d \
126125
drawinglayer/source/primitive2d/graphicprimitive2d \
127126
drawinglayer/source/primitive2d/gridprimitive2d \
128-
drawinglayer/source/primitive2d/groupprimitive2d \
129127
drawinglayer/source/primitive2d/helplineprimitive2d \
130128
drawinglayer/source/primitive2d/hiddengeometryprimitive2d \
131129
drawinglayer/source/primitive2d/invertprimitive2d \

drawinglayer/Library_drawinglayercore.mk

+3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ $(eval $(call gb_Library_use_custom_headers,drawinglayercore,\
4646
$(eval $(call gb_Library_add_exception_objects,drawinglayercore,\
4747
drawinglayer/source/primitive2d/baseprimitive2d \
4848
drawinglayer/source/primitive2d/BufferedDecompositionPrimitive2D \
49+
drawinglayer/source/primitive2d/BufferedDecompositionFlusher \
50+
drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D \
4951
drawinglayer/source/primitive2d/Primitive2DContainer \
52+
drawinglayer/source/primitive2d/groupprimitive2d \
5053
drawinglayer/source/primitive2d/Tools \
5154
drawinglayer/source/geometry/viewinformation2d \
5255
))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2+
/*
3+
* This file is part of the LibreOffice project.
4+
*
5+
* This Source Code Form is subject to the terms of the Mozilla Public
6+
* License, v. 2.0. If a copy of the MPL was not distributed with this
7+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
8+
*
9+
* This file incorporates work covered by the following license notice:
10+
*
11+
* Licensed to the Apache Software Foundation (ASF) under one or more
12+
* contributor license agreements. See the NOTICE file distributed
13+
* with this work for additional information regarding copyright
14+
* ownership. The ASF licenses this file to you under the Apache
15+
* License, Version 2.0 (the "License"); you may not use this file
16+
* except in compliance with the License. You may obtain a copy of
17+
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
18+
*/
19+
20+
#include <sal/config.h>
21+
22+
#include <drawinglayer/primitive2d/BufferedDecompositionFlusher.hxx>
23+
#include <tools/lazydelete.hxx>
24+
25+
namespace drawinglayer::primitive2d
26+
{
27+
/**
28+
This is a "garbage collection" approach to flushing.
29+
30+
We store entries in a set. Every 2 seconds, we scan the set for entries that have not
31+
been used for 10 seconds or more, and if so, we flush the buffer primitives in those entries.
32+
33+
This mechanism is __deliberately__ not perfect.
34+
Sometimes things will be flushed a little too soon, sometimes things will wait a little too long,
35+
since we only have a granularity of 2 seconds.
36+
But what is gains from not being perfect, is scalability.
37+
38+
It is very simple, scales to lots and lots of primitives without needing lots of timers, and performs
39+
very little work in the common case.
40+
*/
41+
42+
static BufferedDecompositionFlusher* getInstance()
43+
{
44+
static tools::DeleteRtlReferenceOnDeinit<BufferedDecompositionFlusher> gaTimer(
45+
new BufferedDecompositionFlusher);
46+
return gaTimer.get().get();
47+
}
48+
49+
// static
50+
void BufferedDecompositionFlusher::update(const BufferedDecompositionPrimitive2D* p)
51+
{
52+
if (auto flusher = getInstance())
53+
flusher->updateImpl(p);
54+
}
55+
56+
// static
57+
void BufferedDecompositionFlusher::update(const BufferedDecompositionGroupPrimitive2D* p)
58+
{
59+
if (auto flusher = getInstance())
60+
flusher->updateImpl(p);
61+
}
62+
63+
BufferedDecompositionFlusher::BufferedDecompositionFlusher()
64+
{
65+
setRemainingTime(salhelper::TTimeValue(2, 0));
66+
start();
67+
}
68+
69+
void BufferedDecompositionFlusher::updateImpl(const BufferedDecompositionPrimitive2D* p)
70+
{
71+
std::unique_lock l(maMutex);
72+
maRegistered1.insert(const_cast<BufferedDecompositionPrimitive2D*>(p));
73+
}
74+
75+
void BufferedDecompositionFlusher::updateImpl(const BufferedDecompositionGroupPrimitive2D* p)
76+
{
77+
std::unique_lock l(maMutex);
78+
maRegistered2.insert(const_cast<BufferedDecompositionGroupPrimitive2D*>(p));
79+
}
80+
81+
void SAL_CALL BufferedDecompositionFlusher::onShot()
82+
{
83+
auto aNow = std::chrono::steady_clock::now();
84+
std::vector<rtl::Reference<BufferedDecompositionPrimitive2D>> aRemoved1;
85+
std::vector<rtl::Reference<BufferedDecompositionGroupPrimitive2D>> aRemoved2;
86+
{
87+
std::unique_lock l(maMutex);
88+
for (auto it = maRegistered1.begin(); it != maRegistered1.end();)
89+
{
90+
if (aNow - (*it)->maLastAccess > std::chrono::seconds(10))
91+
{
92+
aRemoved1.push_back(*it);
93+
it = maRegistered1.erase(it);
94+
}
95+
else
96+
++it;
97+
}
98+
for (auto it = maRegistered2.begin(); it != maRegistered2.end();)
99+
{
100+
if (aNow - (*it)->maLastAccess > std::chrono::seconds(10))
101+
{
102+
aRemoved2.push_back(*it);
103+
it = maRegistered2.erase(it);
104+
}
105+
else
106+
++it;
107+
}
108+
}
109+
for (const auto& r : aRemoved1)
110+
r->setBuffered2DDecomposition(nullptr);
111+
for (const auto& r : aRemoved2)
112+
r->setBuffered2DDecomposition(Primitive2DContainer{});
113+
setRemainingTime(salhelper::TTimeValue(2, 0));
114+
start();
115+
}
116+
117+
} // end of namespace drawinglayer::primitive2d
118+
119+
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

drawinglayer/source/primitive2d/BufferedDecompositionGroupPrimitive2D.cxx

+36-96
Original file line numberDiff line numberDiff line change
@@ -21,140 +21,80 @@
2121

2222
#include <drawinglayer/primitive2d/BufferedDecompositionGroupPrimitive2D.hxx>
2323
#include <drawinglayer/geometry/viewinformation2d.hxx>
24-
25-
namespace
26-
{
27-
class LocalCallbackTimer : public salhelper::Timer
28-
{
29-
protected:
30-
drawinglayer::primitive2d::BufferedDecompositionGroupPrimitive2D* pCustomer;
31-
32-
public:
33-
explicit LocalCallbackTimer(
34-
drawinglayer::primitive2d::BufferedDecompositionGroupPrimitive2D& rCustomer)
35-
: pCustomer(&rCustomer)
36-
{
37-
}
38-
39-
void clearCallback() { pCustomer = nullptr; }
40-
41-
protected:
42-
virtual void SAL_CALL onShot() override;
43-
};
44-
45-
void SAL_CALL LocalCallbackTimer::onShot()
46-
{
47-
if (nullptr != pCustomer)
48-
flushBufferedDecomposition(*pCustomer);
49-
}
50-
}
24+
#include <drawinglayer/primitive2d/BufferedDecompositionFlusher.hxx>
5125

5226
namespace drawinglayer::primitive2d
5327
{
54-
void flushBufferedDecomposition(BufferedDecompositionGroupPrimitive2D& rTarget)
55-
{
56-
rTarget.acquire();
57-
rTarget.setBuffered2DDecomposition(Primitive2DContainer());
58-
rTarget.release();
59-
}
60-
6128
bool BufferedDecompositionGroupPrimitive2D::hasBuffered2DDecomposition() const
6229
{
63-
if (0 != maCallbackSeconds)
64-
{
65-
std::lock_guard Guard(maCallbackLock);
30+
if (!mbFlushOnTimer)
6631
return !maBuffered2DDecomposition.empty();
67-
}
6832
else
6933
{
34+
std::lock_guard Guard(maCallbackLock);
7035
return !maBuffered2DDecomposition.empty();
7136
}
7237
}
7338

7439
void BufferedDecompositionGroupPrimitive2D::setBuffered2DDecomposition(Primitive2DContainer&& rNew)
7540
{
76-
if (0 == maCallbackSeconds)
41+
if (!mbFlushOnTimer)
7742
{
7843
// no flush used, just set
7944
maBuffered2DDecomposition = std::move(rNew);
80-
return;
8145
}
82-
83-
if (maCallbackTimer.is())
84-
{
85-
if (rNew.empty())
86-
{
87-
// stop timer
88-
maCallbackTimer->stop();
89-
}
90-
else
91-
{
92-
// decomposition changed, touch
93-
maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0));
94-
if (!maCallbackTimer->isTicking())
95-
maCallbackTimer->start();
96-
}
97-
}
98-
else if (!rNew.empty())
46+
else
9947
{
100-
// decomposition defined/set/changed, init & start timer
101-
maCallbackTimer.set(new LocalCallbackTimer(*this));
102-
maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0));
103-
maCallbackTimer->start();
104-
}
48+
// decomposition changed, touch
49+
maLastAccess = std::chrono::steady_clock::now();
50+
BufferedDecompositionFlusher::update(this);
10551

106-
// tdf#158913 need to secure change when flush/multithreading is in use
107-
std::lock_guard Guard(maCallbackLock);
108-
maBuffered2DDecomposition = std::move(rNew);
52+
// tdf#158913 need to secure change when flush/multithreading is in use
53+
std::lock_guard Guard(maCallbackLock);
54+
maBuffered2DDecomposition = std::move(rNew);
55+
}
10956
}
11057

11158
BufferedDecompositionGroupPrimitive2D::BufferedDecompositionGroupPrimitive2D(
11259
Primitive2DContainer&& aChildren)
11360
: GroupPrimitive2D(std::move(aChildren))
114-
, maCallbackTimer()
11561
, maCallbackLock()
116-
, maCallbackSeconds(0)
62+
, mbFlushOnTimer(false)
11763
{
11864
}
11965

120-
BufferedDecompositionGroupPrimitive2D::~BufferedDecompositionGroupPrimitive2D()
121-
{
122-
if (maCallbackTimer.is())
123-
{
124-
// no more decomposition, end callback
125-
static_cast<LocalCallbackTimer*>(maCallbackTimer.get())->clearCallback();
126-
maCallbackTimer->stop();
127-
}
128-
}
66+
BufferedDecompositionGroupPrimitive2D::~BufferedDecompositionGroupPrimitive2D() {}
12967

13068
void BufferedDecompositionGroupPrimitive2D::get2DDecomposition(
13169
Primitive2DDecompositionVisitor& rVisitor,
13270
const geometry::ViewInformation2D& rViewInformation) const
13371
{
134-
if (!hasBuffered2DDecomposition())
135-
{
136-
Primitive2DContainer aNewSequence;
137-
create2DDecomposition(aNewSequence, rViewInformation);
138-
const_cast<BufferedDecompositionGroupPrimitive2D*>(this)->setBuffered2DDecomposition(
139-
std::move(aNewSequence));
140-
}
141-
142-
if (0 == maCallbackSeconds)
72+
if (!mbFlushOnTimer)
14373
{
14474
// no flush/multithreading is in use, just call
75+
if (maBuffered2DDecomposition.empty())
76+
create2DDecomposition(maBuffered2DDecomposition, rViewInformation);
14577
rVisitor.visit(maBuffered2DDecomposition);
146-
return;
14778
}
148-
149-
// decomposition was used, touch/restart time
150-
if (maCallbackTimer)
151-
maCallbackTimer->setRemainingTime(salhelper::TTimeValue(maCallbackSeconds, 0));
152-
153-
// tdf#158913 need to secure 'visit' when flush/multithreading is in use,
154-
// so that the local non-ref-Counted instance of the decomposition gets not
155-
// manipulated (e.g. deleted)
156-
std::lock_guard Guard(maCallbackLock);
157-
rVisitor.visit(maBuffered2DDecomposition);
79+
else
80+
{
81+
// tdf#158913 need to secure 'visit' when flush/multithreading is in use,
82+
// so that the local non-ref-Counted instance of the decomposition gets not
83+
// manipulated (e.g. deleted)
84+
Primitive2DContainer xTmp;
85+
{
86+
// only hold the lock for long enough to get a valid reference
87+
std::lock_guard Guard(maCallbackLock);
88+
maLastAccess = std::chrono::steady_clock::now();
89+
if (maBuffered2DDecomposition.empty())
90+
{
91+
create2DDecomposition(maBuffered2DDecomposition, rViewInformation);
92+
BufferedDecompositionFlusher::update(this);
93+
}
94+
xTmp = maBuffered2DDecomposition;
95+
}
96+
rVisitor.visit(xTmp);
97+
}
15898
}
15999

160100
} // end of namespace drawinglayer::primitive2d

0 commit comments

Comments
 (0)