[Libreoffice-commits] online.git: Branch 'distro/collabora/collabora-online-3' - 26 commits - common/Rectangle.hpp kit/Kit.cpp loleaflet/src test/TileCacheTests.cpp wsd/ClientSession.cpp wsd/ClientSession.hpp wsd/DocumentBroker.cpp wsd/DocumentBroker.hpp wsd/protocol.txt wsd/TestStubs.cpp wsd/TileCache.cpp wsd/TileCache.hpp

Libreoffice Gerrit user logerrit at kemper.freedesktop.org
Thu Aug 16 16:39:21 UTC 2018


 common/Rectangle.hpp                         |    5 
 kit/Kit.cpp                                  |    4 
 loleaflet/src/layer/tile/CalcTileLayer.js    |   47 ----
 loleaflet/src/layer/tile/GridLayer.js        |  106 ++++++----
 loleaflet/src/layer/tile/ImpressTileLayer.js |   39 ---
 loleaflet/src/layer/tile/TileLayer.js        |   66 +-----
 loleaflet/src/layer/tile/WriterTileLayer.js  |   41 ----
 test/TileCacheTests.cpp                      |    7 
 wsd/ClientSession.cpp                        |  271 ++++++++++++++++++++++++++-
 wsd/ClientSession.hpp                        |   68 ++++++
 wsd/DocumentBroker.cpp                       |  188 ++++++++++++++----
 wsd/DocumentBroker.hpp                       |    1 
 wsd/TestStubs.cpp                            |   11 +
 wsd/TileCache.cpp                            |   65 +++++-
 wsd/TileCache.hpp                            |    9 
 wsd/protocol.txt                             |    5 
 16 files changed, 658 insertions(+), 275 deletions(-)

New commits:
commit 9f017af051a5093300561ad72baad217bdcd73bd
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Fri Aug 10 17:48:59 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:34:21 2018 +0200

    Update row by row, instead of column by column
    
    Change-Id: I504cab9509d25eebf3f68c63dd7e18a54f80d865
    (cherry picked from commit 6d3a0d03272b9a01fb50254dd2acd67afc6e9ac0)

diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 7c4a0e2ed..428e7d0f9 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -1156,13 +1156,13 @@ void ClientSession::handleTileInvalidation(const std::string& message,
         intersection._y2 = std::min(invalidateRect._y2, _clientVisibleArea._y2);
         if(intersection.isValid()) // Client visible area and invalidated rectangle has intersection
         {
-            for(int i = std::ceil(intersection._x1 / _tileWidthTwips);
-                i <= std::ceil(intersection._x2 / _tileWidthTwips); ++i)
+            for(int i = std::ceil(intersection._y1 / _tileHeightTwips);
+                    i <= std::ceil(intersection._y2 / _tileHeightTwips); ++i)
             {
-                for(int j = std::ceil(intersection._y1 / _tileHeightTwips);
-                    j <= std::ceil(intersection._y2 / _tileHeightTwips); ++j)
+                for(int j = std::ceil(intersection._x1 / _tileWidthTwips);
+                    j <= std::ceil(intersection._x2 / _tileWidthTwips); ++j)
                 {
-                    invalidTiles.emplace_back(TileDesc(part, _tileWidthPixel, _tileHeightPixel, i * _tileWidthTwips, j * _tileHeightTwips, _tileWidthTwips, _tileHeightTwips, -1, 0, -1, false));
+                    invalidTiles.emplace_back(TileDesc(part, _tileWidthPixel, _tileHeightPixel, j * _tileWidthTwips, i * _tileHeightTwips, _tileWidthTwips, _tileHeightTwips, -1, 0, -1, false));
 
                     TileWireId oldWireId = 0;
                     auto iter = _oldWireIds.find(generateTileID(invalidTiles.back()));
commit 83a5c79c2a4bf23d41cda35f480a30eb8e6d653a
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Mon Aug 6 16:58:19 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:34:08 2018 +0200

    Store wireId only for tiles inside the visible area
    
    Change-Id: If60015c86bbdd1158c203a7a9c47b3dc877ac6c5
    (cherry picked from commit 1a885b9c40449a5d491684c7d66674554deed351)

diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 12b2b5eb8..7c4a0e2ed 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -1200,7 +1200,13 @@ void ClientSession::traceTileBySend(const TileDesc& tile)
     }
     else
     {
-        _oldWireIds.insert(std::pair<std::string, TileWireId>(tileID, tile.getWireId()));
+        // Track only tile inside the visible area
+        if(_clientVisibleArea.hasSurface() &&
+           tile.getTilePosX() >= _clientVisibleArea._x1 && tile.getTilePosX() <= _clientVisibleArea._x2 &&
+           tile.getTilePosY() >= _clientVisibleArea._y1 && tile.getTilePosY() <= _clientVisibleArea._y2)
+        {
+            _oldWireIds.insert(std::pair<std::string, TileWireId>(tileID, tile.getWireId()));
+        }
     }
 
     // Record that the tile is sent
commit e74429276037e7face37c7c25d0f3c37d45b1be9
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Mon Aug 6 14:17:45 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:33:53 2018 +0200

    Need to reset wireId map anytime when part number changes
    
    Change-Id: I8309a0a0788587f6daebe9698723df6bc0410039
    (cherry picked from commit e35ce41eaa0986ebc6ae497ee5883ede2566b58f)

diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index c9fad0456..12b2b5eb8 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -736,10 +736,12 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
             if(getTokenInteger(tokens[1], "part", setPart))
             {
                 _clientSelectedPart = setPart;
+                resetWireIdMap();
             }
             else if (stringToInteger(tokens[1], setPart))
             {
                 _clientSelectedPart = setPart;
+                resetWireIdMap();
             }
             else
                 return false;
commit 6aafd6c52cdb67c189d9aaea91029583d654797e
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Fri Aug 3 13:50:40 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:33:24 2018 +0200

    Disable testCancelTilesMultiView
    
    It's seems unstable. After canceltiles wsd still can send tiles
    if they already in the senderqueue.
    
    Change-Id: I28f669aa18dfbfee1d9d242bd1ee3d0490f06c68
    (cherry picked from commit 4d1ffab9413fdbc1636686b07d88df785485766a)

diff --git a/test/TileCacheTests.cpp b/test/TileCacheTests.cpp
index 112d555ea..54f177068 100644
--- a/test/TileCacheTests.cpp
+++ b/test/TileCacheTests.cpp
@@ -60,7 +60,8 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture
     CPPUNIT_TEST(testSimpleCombine);
     CPPUNIT_TEST(testPerformance);
     CPPUNIT_TEST(testCancelTiles);
-    CPPUNIT_TEST(testCancelTilesMultiView);
+    // unstable
+    // CPPUNIT_TEST(testCancelTilesMultiView);
     CPPUNIT_TEST(testDisconnectMultiView);
     CPPUNIT_TEST(testUnresponsiveClient);
     CPPUNIT_TEST(testImpressTiles);
commit f9675461b40d0682ddad83c461caaf00d8feb7ce
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Jul 31 15:42:58 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:32:45 2018 +0200

    Use a bigger number as a tiles-on-fly limit
    
    So scrolling can be more smooth.
    
    Change-Id: I7b029c0ccc2de6883db54493a9188ae54a346a1d
    (cherry picked from commit 165e5b4e1cf4c62e4667c6ad118479062e49b1d2)
    
    Enough to have smaller tiles-on-fly limit
    
    Change-Id: I7e9e1b2c117cb8938b6f0fb2eac8ab3e2c8fef30
    (cherry picked from commit 3bc8821bb0c7fcd887376367519fa8623a09962a)

diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index a5e1a5d31..36733fd2c 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -41,7 +41,7 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 
-#define TILES_ON_FLY_MIN_UPPER_LIMIT 10u
+#define TILES_ON_FLY_MIN_UPPER_LIMIT 10.0f
 
 using namespace LOOLProtocol;
 
@@ -1368,13 +1368,13 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
     std::unique_lock<std::mutex> lock(_mutex);
 
     // How many tiles we have on the visible area, set the upper limit accordingly
-    const unsigned tilesFitOnWidth = static_cast<unsigned>(std::ceil(static_cast<float>(session->getVisibleArea().getWidth()) /
-                                                                     static_cast<float>(session->getTileWidthInTwips())));
-    const unsigned tilesFitOnHeight = static_cast<unsigned>(std::ceil(static_cast<float>(session->getVisibleArea().getHeight()) /
-                                                                      static_cast<float>(session->getTileHeightInTwips())));
-    const unsigned tilesInVisArea = tilesFitOnWidth * tilesFitOnHeight;
+    const float tilesFitOnWidth = static_cast<float>(session->getVisibleArea().getWidth()) /
+                                  static_cast<float>(session->getTileWidthInTwips());
+    const float tilesFitOnHeight = static_cast<float>(session->getVisibleArea().getHeight()) /
+                                   static_cast<float>(session->getTileHeightInTwips());
+    const float tilesInVisArea = tilesFitOnWidth * tilesFitOnHeight;
 
-    const unsigned tilesOnFlyUpperLimit = std::max(TILES_ON_FLY_MIN_UPPER_LIMIT, tilesInVisArea);
+    const float tilesOnFlyUpperLimit = std::max(TILES_ON_FLY_MIN_UPPER_LIMIT, tilesInVisArea * 1.20f);
 
     // Update client's tilesBeingRendered list
     session->removeOutdatedTileSubscriptions();
commit 15590cc96d3ab3c1b55f7e8fe641c91d2f2b9c2a
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Jul 31 14:47:27 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:27:06 2018 +0200

    Make client tilesBeingRendered tracking more robust
    
    Store the tile cache names and drop outdated tiles
    times to times, so we can avoid tile rendering / sending to
    stuck.
    
    (cherry picked from commit 8d95ca716568272f6246d959aeda5109adefa5a3)
    
    Change-Id: Ibff001307c7c660cbc57ab20c29c430e0090444d

diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 07ae356af..c9fad0456 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -1205,6 +1205,38 @@ void ClientSession::traceTileBySend(const TileDesc& tile)
     addTileOnFly(tile);
 }
 
+void ClientSession::traceSubscribeToTile(const std::string& cacheName)
+{
+    _tilesBeingRendered.insert(cacheName);
+}
+
+void ClientSession::traceUnSubscribeToTile(const std::string& cacheName)
+{
+    _tilesBeingRendered.erase(cacheName);
+}
+
+void ClientSession::removeOutdatedTileSubscriptions()
+{
+    const std::shared_ptr<DocumentBroker> docBroker = getDocumentBroker();
+    if(!docBroker)
+        return;
+
+    auto iterator = _tilesBeingRendered.begin();
+    while(iterator != _tilesBeingRendered.end())
+    {
+        double elapsedTime = docBroker->tileCache().getTileBeingRenderedElapsedTimeMs(*iterator);
+        if(elapsedTime < 0.0 && elapsedTime > 5000.0)
+            _tilesBeingRendered.erase(iterator);
+        else
+            ++iterator;
+    }
+}
+
+void ClientSession::clearTileSubscription()
+{
+    _tilesBeingRendered.clear();
+}
+
 std::string ClientSession::generateTileID(const TileDesc& tile)
 {
     std::ostringstream tileID;
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index c1b502854..824f7adc4 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -20,6 +20,7 @@
 #include <boost/optional.hpp>
 #include <list>
 #include <map>
+#include <unordered_set>
 
 class DocumentBroker;
 
@@ -125,11 +126,13 @@ public:
     /// Call this method anytime when a new tile is sent to the client
     void traceTileBySend(const TileDesc& tile);
 
-    void traceSubscribe() { ++_tilesBeingRendered; }
-    void traceUnSubscribe() { --_tilesBeingRendered; }
-    void clearSubscription() { _tilesBeingRendered = 0; }
+    /// Trask tiles what we a subscription to
+    void traceSubscribeToTile(const std::string& tileCacheName);
+    void traceUnSubscribeToTile(const std::string& tileCacheName);
+    void removeOutdatedTileSubscriptions();
+    void clearTileSubscription();
 
-    int getTilesBeingRendered() const {return _tilesBeingRendered;}
+    size_t getTilesBeingRenderedCount() const {return _tilesBeingRendered.size();}
 private:
 
     /// SocketHandler: disconnection event.
@@ -221,9 +224,9 @@ private:
     /// TileID's of the sent tiles. Push by sending and pop by tileprocessed message from the client.
     std::list<std::string> _tilesOnFly;
 
-    /// Number of tiles requested from kit, which this session is subsrcibed to
+    /// Names of tiles requested from kit, which this session is subsrcibed to
     /// Track only non-thumbnail tiles (getId() == -1)
-    int _tilesBeingRendered;
+    std::unordered_set<std::string> _tilesBeingRendered;
 
     /// Requested tiles are stored in this list, before we can send them to the client
     boost::optional<std::list<TileDesc>> _requestedTiles;
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 7bf69bc0a..a5e1a5d31 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -1376,13 +1376,16 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
 
     const unsigned tilesOnFlyUpperLimit = std::max(TILES_ON_FLY_MIN_UPPER_LIMIT, tilesInVisArea);
 
+    // Update client's tilesBeingRendered list
+    session->removeOutdatedTileSubscriptions();
+
     // All tiles were processed on client side what we sent last time, so we can send a new banch of tiles
     // which was invalidated / requested in the meantime
     boost::optional<std::list<TileDesc>>& requestedTiles = session->getRequestedTiles();
     if(requestedTiles != boost::none && !requestedTiles.get().empty())
     {
         std::vector<TileDesc> tilesNeedsRendering;
-        while(session->getTilesOnFlyCount() + session->getTilesBeingRendered() < tilesOnFlyUpperLimit
+        while(session->getTilesOnFlyCount() + session->getTilesBeingRenderedCount() < tilesOnFlyUpperLimit
               && !requestedTiles.get().empty())
         {
             TileDesc& tile = *(requestedTiles.get().begin());
diff --git a/wsd/TestStubs.cpp b/wsd/TestStubs.cpp
index 4dd80cbfd..d5407c11b 100644
--- a/wsd/TestStubs.cpp
+++ b/wsd/TestStubs.cpp
@@ -22,4 +22,10 @@ void DocumentBroker::assertCorrectThread() const {}
 
 void ClientSession::traceTileBySend(const TileDesc& /*tile*/) {}
 
+void ClientSession::traceSubscribeToTile(const std::string& /*tileCacheName*/) {};
+
+void ClientSession::traceUnSubscribeToTile(const std::string& /*tileCacheName*/) {};
+
+void ClientSession::clearTileSubscription() {};
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/TileCache.cpp b/wsd/TileCache.cpp
index ee3271fa2..cee016b3e 100644
--- a/wsd/TileCache.cpp
+++ b/wsd/TileCache.cpp
@@ -130,12 +130,21 @@ void TileCache::forgetTileBeingRendered(std::shared_ptr<TileCache::TileBeingRend
     {
         std::shared_ptr<ClientSession> session = subscriber.lock();
         if(session && tile.getId() == -1)
-            session->traceUnSubscribe();
+            session->traceUnSubscribeToTile(tileBeingRendered->getCacheName());
     }
 
     _tilesBeingRendered.erase(tileBeingRendered->getCacheName());
 }
 
+double TileCache::getTileBeingRenderedElapsedTimeMs(const std::string& tileCacheName) const
+{
+    auto iterator = _tilesBeingRendered.find(tileCacheName);
+    if(iterator == _tilesBeingRendered.end())
+        return -1.0; // Negativ value means that we did not find tileBeingRendered object
+
+    return iterator->second->getElapsedTimeMs();
+}
+
 std::unique_ptr<std::fstream> TileCache::lookupTile(const TileDesc& tile)
 {
     const std::string fileName = _cacheDir + "/" + cacheFileName(tile);
@@ -493,7 +502,7 @@ void TileCache::subscribeToTileRendering(const TileDesc& tile, const std::shared
                 tileBeingRendered->_subscribers.size() << " subscribers already.");
         tileBeingRendered->_subscribers.push_back(subscriber);
         if(tile.getId() == -1)
-            subscriber->traceSubscribe();
+            subscriber->traceSubscribeToTile(tileBeingRendered->getCacheName());
 
         const auto duration = (std::chrono::steady_clock::now() - tileBeingRendered->getStartTime());
         if (std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() > COMMAND_TIMEOUT_MS)
@@ -514,7 +523,7 @@ void TileCache::subscribeToTileRendering(const TileDesc& tile, const std::shared
         tileBeingRendered = std::make_shared<TileBeingRendered>(cachedName, tile);
         tileBeingRendered->_subscribers.push_back(subscriber);
         if(tile.getId() == -1)
-            subscriber->traceSubscribe();
+            subscriber->traceSubscribeToTile(tileBeingRendered->getCacheName());
         _tilesBeingRendered[cachedName] = tileBeingRendered;
     }
 }
@@ -561,7 +570,8 @@ std::string TileCache::cancelTiles(const std::shared_ptr<ClientSession> &subscri
         ++it;
     }
 
-    subscriber->clearSubscription();
+    if(sub)
+        sub->clearTileSubscription();
     const auto canceltiles = oss.str();
     return canceltiles.empty() ? canceltiles : "canceltiles " + canceltiles;
 }
diff --git a/wsd/TileCache.hpp b/wsd/TileCache.hpp
index 63adad265..e4573b465 100644
--- a/wsd/TileCache.hpp
+++ b/wsd/TileCache.hpp
@@ -80,6 +80,7 @@ public:
     void saveLastModified(const Poco::Timestamp& timestamp);
 
     void forgetTileBeingRendered(std::shared_ptr<TileCache::TileBeingRendered> tileBeingRendered, const TileDesc& tile);
+    double getTileBeingRenderedElapsedTimeMs(const std::string& tileCacheName) const;
 
     void setThreadOwner(const std::thread::id &id) { _owner = id; }
     void assertCorrectThread();
commit d1c26519179fddf308222dd1cf9cfeb5d69d19dc
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Mon Jul 23 16:09:55 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:21:18 2018 +0200

    Store number of tiles sent to kit for rendering
    
    and use that info also to avoid sending to much tiles on the network.
    
    (cherry picked from commit c2a5f6acb0f1e93f19104b761661c852d930fb9e)
    
    Change-Id: Iab2d7af64693047a3c1cfe9f73de80a7100bbc13
    
    Unused method
    
    Change-Id: I53c2a33313fbcd3cd0484c0e8a27985c673ad04e
    (cherry picked from commit 30cdbc330b0fe0018bf14c9c932306461382b110)

diff --git a/kit/Kit.cpp b/kit/Kit.cpp
index b688e760d..ab5b384e1 100644
--- a/kit/Kit.cpp
+++ b/kit/Kit.cpp
@@ -1022,7 +1022,9 @@ public:
                 // The tile content is identical to what the client already has, so skip it
                 LOG_TRC("Match for tile #" << tileIndex << " at (" << positionX << "," <<
                         positionY << ") oldhash==hash (" << hash << "), wireId: " << wireId << " skipping");
-                tiles.erase(tiles.begin() + tileIndex);
+                tiles[tileIndex].setWireId(wireId);
+                tiles[tileIndex].setImgSize(0);
+                tileIndex++;
                 continue;
             }
 
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index c9d48e904..07ae356af 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -50,7 +50,9 @@ ClientSession::ClientSession(const std::string& id,
     _tileHeightPixel(0),
     _tileWidthTwips(0),
     _tileHeightTwips(0),
-    _isTextDocument(false)
+    _isTextDocument(false),
+    _tilesOnFly(0),
+    _tilesBeingRendered(0)
 {
     const size_t curConnections = ++LOOLWSD::NumConnections;
     LOG_INF("ClientSession ctor [" << getName() << "], current number of connections: " << curConnections);
@@ -343,6 +345,8 @@ bool ClientSession::_handleInput(const char *buffer, int length)
         auto iter = std::find(_tilesOnFly.begin(), _tilesOnFly.end(), tileID);
         if(iter != _tilesOnFly.end())
             _tilesOnFly.erase(iter);
+        else
+            LOG_WRN("Tileprocessed message with an unknown tile ID");
 
         docBroker->sendRequestedTiles(shared_from_this());
         return true;
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index b0ce33589..c1b502854 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -124,6 +124,12 @@ public:
     /// This method updates internal data related to sent tiles (wireID and tiles-on-fly)
     /// Call this method anytime when a new tile is sent to the client
     void traceTileBySend(const TileDesc& tile);
+
+    void traceSubscribe() { ++_tilesBeingRendered; }
+    void traceUnSubscribe() { --_tilesBeingRendered; }
+    void clearSubscription() { _tilesBeingRendered = 0; }
+
+    int getTilesBeingRendered() const {return _tilesBeingRendered;}
 private:
 
     /// SocketHandler: disconnection event.
@@ -215,6 +221,10 @@ private:
     /// TileID's of the sent tiles. Push by sending and pop by tileprocessed message from the client.
     std::list<std::string> _tilesOnFly;
 
+    /// Number of tiles requested from kit, which this session is subsrcibed to
+    /// Track only non-thumbnail tiles (getId() == -1)
+    int _tilesBeingRendered;
+
     /// Requested tiles are stored in this list, before we can send them to the client
     boost::optional<std::list<TileDesc>> _requestedTiles;
 
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 53b554ea8..7bf69bc0a 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -1382,7 +1382,8 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
     if(requestedTiles != boost::none && !requestedTiles.get().empty())
     {
         std::vector<TileDesc> tilesNeedsRendering;
-        while(session->getTilesOnFlyCount() < tilesOnFlyUpperLimit && !requestedTiles.get().empty())
+        while(session->getTilesOnFlyCount() + session->getTilesBeingRendered() < tilesOnFlyUpperLimit
+              && !requestedTiles.get().empty())
         {
             TileDesc& tile = *(requestedTiles.get().begin());
 
@@ -1417,15 +1418,9 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
             else
             {
                 // Not cached, needs rendering.
-                if(tile.getVersion() == -1) // Rendering of this tile was not requested yet
-                {
-                    tile.setVersion(++_tileVersion);
-                }
-                if(!tileCache().hasTileBeingRendered(tile))
-                {
-                    tilesNeedsRendering.push_back(tile);
-                    _debugRenderedTileCount++;
-                }
+                tile.setVersion(++_tileVersion);
+                tilesNeedsRendering.push_back(tile);
+                _debugRenderedTileCount++;
                 tileCache().subscribeToTileRendering(tile, session);
             }
             requestedTiles.get().pop_front();
@@ -1497,25 +1492,16 @@ void DocumentBroker::handleTileCombinedResponse(const std::vector<char>& payload
 
     try
     {
-        const auto length = payload.size();
-        if (firstLine.size() < static_cast<std::string::size_type>(length) - 1)
-        {
-            const auto tileCombined = TileCombined::parse(firstLine);
-            const auto buffer = payload.data();
-            auto offset = firstLine.size() + 1;
+        const auto tileCombined = TileCombined::parse(firstLine);
+        const auto buffer = payload.data();
+        auto offset = firstLine.size() + 1;
 
-            std::unique_lock<std::mutex> lock(_mutex);
+        std::unique_lock<std::mutex> lock(_mutex);
 
-            for (const auto& tile : tileCombined.getTiles())
-            {
-                tileCache().saveTileAndNotify(tile, buffer + offset, tile.getImgSize());
-                offset += tile.getImgSize();
-            }
-        }
-        else
+        for (const auto& tile : tileCombined.getTiles())
         {
-            LOG_WRN("Dropping empty tilecombine response: " << firstLine);
-            // They will get re-issued if we don't forget them.
+            tileCache().saveTileAndNotify(tile, buffer + offset, tile.getImgSize());
+            offset += tile.getImgSize();
         }
     }
     catch (const std::exception& exc)
diff --git a/wsd/TileCache.cpp b/wsd/TileCache.cpp
index 629a273c1..ee3271fa2 100644
--- a/wsd/TileCache.cpp
+++ b/wsd/TileCache.cpp
@@ -120,18 +120,20 @@ std::shared_ptr<TileCache::TileBeingRendered> TileCache::findTileBeingRendered(c
     return tile != _tilesBeingRendered.end() ? tile->second : nullptr;
 }
 
-void TileCache::forgetTileBeingRendered(const TileDesc& tile)
+void TileCache::forgetTileBeingRendered(std::shared_ptr<TileCache::TileBeingRendered> tileBeingRendered, const TileDesc& tile)
 {
-    const std::string cachedName = cacheFileName(tile);
-
     assertCorrectThread();
+    assert(tileBeingRendered);
+    assert(_tilesBeingRendered.find(tileBeingRendered->getCacheName()) != _tilesBeingRendered.end());
 
-    _tilesBeingRendered.erase(cachedName);
-}
+    for(auto& subscriber : tileBeingRendered->_subscribers)
+    {
+        std::shared_ptr<ClientSession> session = subscriber.lock();
+        if(session && tile.getId() == -1)
+            session->traceUnSubscribe();
+    }
 
-bool TileCache::hasTileBeingRendered(const TileDesc& tile)
-{
-    return findTileBeingRendered(tile) != nullptr;
+    _tilesBeingRendered.erase(tileBeingRendered->getCacheName());
 }
 
 std::unique_ptr<std::fstream> TileCache::lookupTile(const TileDesc& tile)
@@ -158,6 +160,17 @@ void TileCache::saveTileAndNotify(const TileDesc& tile, const char *data, const
 
     std::shared_ptr<TileBeingRendered> tileBeingRendered = findTileBeingRendered(tile);
 
+    // Kit did not send image data, because tile has the same wireID as the previously sent tile
+    // We need to remove only the subscriptions
+    if(size == 0)
+    {
+        if(tileBeingRendered && tileBeingRendered->getVersion() <= tile.getVersion())
+        {
+            forgetTileBeingRendered(tileBeingRendered, tile);
+        }
+        return;
+    }
+
     // Save to disk.
     const auto cachedName = (tileBeingRendered ? tileBeingRendered->getCacheName()
                                                : cacheFileName(tile));
@@ -228,7 +241,7 @@ void TileCache::saveTileAndNotify(const TileDesc& tile, const char *data, const
         {
             LOG_DBG("STATISTICS: tile " << tile.getVersion() << " internal roundtrip " <<
                     tileBeingRendered->getElapsedTimeMs() << " ms.");
-            _tilesBeingRendered.erase(cachedName);
+            forgetTileBeingRendered(tileBeingRendered, tile);
         }
     }
     else
@@ -479,6 +492,8 @@ void TileCache::subscribeToTileRendering(const TileDesc& tile, const std::shared
         LOG_DBG("Subscribing " << subscriber->getName() << " to tile " << name << " which has " <<
                 tileBeingRendered->_subscribers.size() << " subscribers already.");
         tileBeingRendered->_subscribers.push_back(subscriber);
+        if(tile.getId() == -1)
+            subscriber->traceSubscribe();
 
         const auto duration = (std::chrono::steady_clock::now() - tileBeingRendered->getStartTime());
         if (std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() > COMMAND_TIMEOUT_MS)
@@ -498,6 +513,8 @@ void TileCache::subscribeToTileRendering(const TileDesc& tile, const std::shared
 
         tileBeingRendered = std::make_shared<TileBeingRendered>(cachedName, tile);
         tileBeingRendered->_subscribers.push_back(subscriber);
+        if(tile.getId() == -1)
+            subscriber->traceSubscribe();
         _tilesBeingRendered[cachedName] = tileBeingRendered;
     }
 }
@@ -544,6 +561,7 @@ std::string TileCache::cancelTiles(const std::shared_ptr<ClientSession> &subscri
         ++it;
     }
 
+    subscriber->clearSubscription();
     const auto canceltiles = oss.str();
     return canceltiles.empty() ? canceltiles : "canceltiles " + canceltiles;
 }
diff --git a/wsd/TileCache.hpp b/wsd/TileCache.hpp
index 560b66ccc..63adad265 100644
--- a/wsd/TileCache.hpp
+++ b/wsd/TileCache.hpp
@@ -79,8 +79,7 @@ public:
     /// Store the timestamp to modtime.txt.
     void saveLastModified(const Poco::Timestamp& timestamp);
 
-    void forgetTileBeingRendered(const TileDesc& tile);
-    bool hasTileBeingRendered(const TileDesc& tile);
+    void forgetTileBeingRendered(std::shared_ptr<TileCache::TileBeingRendered> tileBeingRendered, const TileDesc& tile);
 
     void setThreadOwner(const std::thread::id &id) { _owner = id; }
     void assertCorrectThread();
commit 9e99f74bee3f375108fd7c9d7d5da80f3139610c
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Thu Jul 19 14:19:07 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:11:12 2018 +0200

    Fix failing unit tests after latency changes
    
    With the new code, wsd is waiting for tileprocessed messages
    if the upper limit of tiles-on-fly limit is reached. To avoid that
    send canceltiles message.
    
    Change-Id: Iadf16c834f12d14000d630078882dfa8e11a99a0
    (cherry picked from commit 33a0cb1ee7f1b3e6a3e234a257a6db1848ba01bc)

diff --git a/test/TileCacheTests.cpp b/test/TileCacheTests.cpp
index cc23b49c7..112d555ea 100644
--- a/test/TileCacheTests.cpp
+++ b/test/TileCacheTests.cpp
@@ -243,6 +243,8 @@ void TileCacheTests::testPerformance()
             auto tile = getResponseMessage(socket, "tile:", "tile-performance ");
             CPPUNIT_ASSERT_MESSAGE("did not receive a tile: message as expected", !tile.empty());
         }
+        /// Send canceltiles message to clear tiles-on-fly list, otherwise wsd waits for tileprocessed messages
+        sendTextFrame(socket, "canceltiles");
     }
 
     std::cerr << "Tile rendering roundtrip for 5 x 8 tiles combined: " << timestamp.elapsed() / 1000.
@@ -426,6 +428,8 @@ void TileCacheTests::testUnresponsiveClient()
             auto tile = getResponseMessage(socket2, "tile:", "client2 ");
             CPPUNIT_ASSERT_MESSAGE("Did not receive tile #" + std::to_string(i+1) + " of 8: message as expected", !tile.empty());
         }
+        /// Send canceltiles message to clear tiles-on-fly list, otherwise wsd waits for tileprocessed messages
+        sendTextFrame(socket2, "canceltiles");
     }
 }
 
commit 36c0b077cdc67a8c9b8875dbd3da1d3551381602
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Jul 10 14:23:20 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:10:40 2018 +0200

    Add some additional comment related to latency changes
    
    Change-Id: I3ece60ce8a66730a8f8a93757412bcaa2b02a77d
    (cherry picked from commit fe5507f134c23198eb78276836b619fa227600fa)

diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index ebdbdcbc7..b0ce33589 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -109,8 +109,10 @@ public:
     /// Set WOPI fileinfo object
     void setWopiFileInfo(std::unique_ptr<WopiStorage::WOPIFileInfo>& wopiFileInfo) { _wopiFileInfo = std::move(wopiFileInfo); }
 
+    /// Get requested tiles waiting for sending to the client
     boost::optional<std::list<TileDesc>>& getRequestedTiles() { return _requestedTiles; }
 
+    /// Mark a new tile as sent
     void addTileOnFly(const TileDesc& tile);
     void clearTilesOnFly();
     size_t getTilesOnFlyCount() const { return _tilesOnFly.size(); }
@@ -159,12 +161,14 @@ private:
 
     void dumpState(std::ostream& os) override;
 
+    /// Handle invalidation message comming from a kit and transfer it to a tile request.
     void handleTileInvalidation(const std::string& message,
                                 const std::shared_ptr<DocumentBroker>& docBroker);
 
     /// Clear wireId map anytime when client visible area changes (visible area, zoom, part number)
     void resetWireIdMap();
 
+    /// Generate a unique id for a tile
     std::string generateTileID(const TileDesc& tile);
 
 private:
@@ -208,11 +212,13 @@ private:
     /// Client is using a text document?
     bool _isTextDocument;
 
+    /// TileID's of the sent tiles. Push by sending and pop by tileprocessed message from the client.
     std::list<std::string> _tilesOnFly;
 
+    /// Requested tiles are stored in this list, before we can send them to the client
     boost::optional<std::list<TileDesc>> _requestedTiles;
 
-    /// Store wireID's of the sent tiles for the actual visible area
+    /// Store wireID's of the sent tiles inside the actual visible area
     std::map<std::string, TileWireId> _oldWireIds;
 };
 
commit 162380f03dcedb94379bde0540db1c0520bcb2a9
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Fri Jul 6 13:48:30 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:10:23 2018 +0200

    Set back debugging code removed by latency related code changes
    
    Change-Id: I6634029bc8c36dfea7219e7e48e1c010b274e687
    (cherry picked from commit 3215ddfe481e00cf75191841ee052a38fc30104c)

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index a18f7e84f..270ff8e2c 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -253,6 +253,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 		var visibleBottomRight = this._latLngToTwips(this._map.getBounds().getSouthEast());
 		var visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight);
 
+		var needsNewTiles = false;
 		for (var key in this._tiles) {
 			var coords = this._tiles[key].coords;
 			var tileTopLeft = this._coordsToTwips(coords);
@@ -266,6 +267,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 					this._tiles[key]._invalidCount = 1;
 				}
 				if (visibleArea.intersects(bounds)) {
+					needsNewTiles = true;
 					if (this._debug) {
 						this._debugAddInvalidationData(this._tiles[key]);
 					}
@@ -278,6 +280,11 @@ L.CalcTileLayer = L.TileLayer.extend({
 			}
 		}
 
+		if (needsNewTiles && command.part === this._selectedPart && this._debug)
+		{
+			this._debugAddInvalidationMessage(textMsg);
+		}
+
 		for (key in this._tileCache) {
 			// compute the rectangle that each tile covers in the document based
 			// on the zoom level
diff --git a/loleaflet/src/layer/tile/GridLayer.js b/loleaflet/src/layer/tile/GridLayer.js
index 7f252d1a2..720fc2697 100644
--- a/loleaflet/src/layer/tile/GridLayer.js
+++ b/loleaflet/src/layer/tile/GridLayer.js
@@ -703,6 +703,13 @@ L.GridLayer = L.Layer.extend({
 			// Visible area is dirty, update it on the server
 			this._clientVisibleArea = newClientVisibleArea
 			this._map._socket.sendMessage(this._clientVisibleArea);
+			if (this._debug) {
+				this._debugInfo.clearLayers();
+				for (var key in this._tiles) {
+					this._tiles[key]._debugPopup = null;
+					this._tiles[key]._debugTile = null;
+				}
+			}
 		}
 	},
 
diff --git a/loleaflet/src/layer/tile/ImpressTileLayer.js b/loleaflet/src/layer/tile/ImpressTileLayer.js
index 0cf2f8247..e2a4ba308 100644
--- a/loleaflet/src/layer/tile/ImpressTileLayer.js
+++ b/loleaflet/src/layer/tile/ImpressTileLayer.js
@@ -356,7 +356,7 @@ L.ImpressTileLayer = L.TileLayer.extend({
 		var visibleTopLeft = this._latLngToTwips(this._map.getBounds().getNorthWest());
 		var visibleBottomRight = this._latLngToTwips(this._map.getBounds().getSouthEast());
 		var visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight);
-
+		var needsNewTiles = false;
 		for (var key in this._tiles) {
 			var coords = this._tiles[key].coords;
 			var tileTopLeft = this._coordsToTwips(coords);
@@ -370,6 +370,7 @@ L.ImpressTileLayer = L.TileLayer.extend({
 					this._tiles[key]._invalidCount = 1;
 				}
 				if (visibleArea.intersects(bounds)) {
+					needsNewTiles = true;
 					if (this._debug) {
 						this._debugAddInvalidationData(this._tiles[key]);
 					}
@@ -382,6 +383,11 @@ L.ImpressTileLayer = L.TileLayer.extend({
 			}
 		}
 
+		if (needsNewTiles && command.part === this._selectedPart && this._debug)
+		{
+			this._debugAddInvalidationMessage(textMsg);
+		}
+
 		for (key in this._tileCache) {
 			// compute the rectangle that each tile covers in the document based
 			// on the zoom level
diff --git a/loleaflet/src/layer/tile/WriterTileLayer.js b/loleaflet/src/layer/tile/WriterTileLayer.js
index 8c9f46e81..f7bca842b 100644
--- a/loleaflet/src/layer/tile/WriterTileLayer.js
+++ b/loleaflet/src/layer/tile/WriterTileLayer.js
@@ -109,6 +109,7 @@ L.WriterTileLayer = L.TileLayer.extend({
 		var visibleTopLeft = this._latLngToTwips(this._map.getBounds().getNorthWest());
 		var visibleBottomRight = this._latLngToTwips(this._map.getBounds().getSouthEast());
 		var visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight);
+		var needsNewTiles = false;
 		for (var key in this._tiles) {
 			var coords = this._tiles[key].coords;
 			var tileTopLeft = this._coordsToTwips(coords);
@@ -122,6 +123,7 @@ L.WriterTileLayer = L.TileLayer.extend({
 					this._tiles[key]._invalidCount = 1;
 				}
 				if (visibleArea.intersects(bounds)) {
+					needsNewTiles = true;
 					if (this._debug) {
 						this._debugAddInvalidationData(this._tiles[key]);
 					}
@@ -134,6 +136,11 @@ L.WriterTileLayer = L.TileLayer.extend({
 			}
 		}
 
+		if (needsNewTiles && this._debug)
+		{
+			this._debugAddInvalidationMessage(textMsg);
+		}
+
 		for (key in this._tileCache) {
 			// compute the rectangle that each tile covers in the document based
 			// on the zoom level
commit 8be98effdcd6ed058ea8e8fdb96dc1d1ffd98d75
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Fri Jul 6 12:38:31 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:10:02 2018 +0200

    Store wiredIDs on the server side
    
    So we can use this information in tile requests.
    
    (cherry picked from commit 8d92b0809de30faeef3819022be6628f634f85f2)
    
    Change-Id: I87ba420ec0fd699353d48a228268e546ace21921

diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 70a1bfa93..d5575ce91 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -1315,9 +1315,6 @@ L.TileLayer = L.GridLayer.extend({
 			});
 		}
 		else if (tile) {
-			if (command.wireId != undefined) {
-				tile.oldWireId = command.wireId;
-			}
 			if (this._tiles[key]._invalidCount > 0) {
 				this._tiles[key]._invalidCount -= 1;
 			}
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index af15ac74a..c9d48e904 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -286,6 +286,7 @@ bool ClientSession::_handleInput(const char *buffer, int length)
         else
         {
             _clientVisibleArea = Util::Rectangle(x, y, width, height);
+            resetWireIdMap();
             return forwardToChild(std::string(buffer, length), docBroker);
         }
     }
@@ -303,6 +304,7 @@ bool ClientSession::_handleInput(const char *buffer, int length)
             else
             {
                 _clientSelectedPart = temp;
+                resetWireIdMap();
                 return forwardToChild(std::string(buffer, length), docBroker);
             }
         }
@@ -325,6 +327,7 @@ bool ClientSession::_handleInput(const char *buffer, int length)
             _tileHeightPixel = tilePixelHeight;
             _tileWidthTwips = tileTwipWidth;
             _tileHeightTwips = tileTwipHeight;
+            resetWireIdMap();
             return forwardToChild(std::string(buffer, length), docBroker);
         }
     }
@@ -895,6 +898,7 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
                 if(getTokenInteger(token, "current", part))
                 {
                     _clientSelectedPart = part;
+                    resetWireIdMap();
                 }
 
                 // Get document type too
@@ -1028,10 +1032,7 @@ Authorization ClientSession::getAuthorization() const
 
 void ClientSession::addTileOnFly(const TileDesc& tile)
 {
-    std::ostringstream tileID;
-    tileID << tile.getPart() << ":" << tile.getTilePosX() << ":" << tile.getTilePosY() << ":"
-           << tile.getTileWidth() << ":" << tile.getTileHeight();
-    _tilesOnFly.push_back(tileID.str());
+    _tilesOnFly.push_back(generateTileID(tile));
 }
 
 void ClientSession::clearTilesOnFly()
@@ -1156,7 +1157,13 @@ void ClientSession::handleTileInvalidation(const std::string& message,
                     j <= std::ceil(intersection._y2 / _tileHeightTwips); ++j)
                 {
                     invalidTiles.emplace_back(TileDesc(part, _tileWidthPixel, _tileHeightPixel, i * _tileWidthTwips, j * _tileHeightTwips, _tileWidthTwips, _tileHeightTwips, -1, 0, -1, false));
-                    invalidTiles.back().setOldWireId(0);
+
+                    TileWireId oldWireId = 0;
+                    auto iter = _oldWireIds.find(generateTileID(invalidTiles.back()));
+                    if(iter != _oldWireIds.end())
+                        oldWireId = iter->second;
+
+                    invalidTiles.back().setOldWireId(oldWireId);
                     invalidTiles.back().setWireId(0);
                 }
             }
@@ -1170,4 +1177,36 @@ void ClientSession::handleTileInvalidation(const std::string& message,
     }
 }
 
+void ClientSession::resetWireIdMap()
+{
+    _oldWireIds.clear();
+}
+
+void ClientSession::traceTileBySend(const TileDesc& tile)
+{
+    const std::string tileID = generateTileID(tile);
+
+    // Store wireId first
+    auto iter = _oldWireIds.find(tileID);
+    if(iter != _oldWireIds.end())
+    {
+        iter->second = tile.getWireId();
+    }
+    else
+    {
+        _oldWireIds.insert(std::pair<std::string, TileWireId>(tileID, tile.getWireId()));
+    }
+
+    // Record that the tile is sent
+    addTileOnFly(tile);
+}
+
+std::string ClientSession::generateTileID(const TileDesc& tile)
+{
+    std::ostringstream tileID;
+    tileID << tile.getPart() << ":" << tile.getTilePosX() << ":" << tile.getTilePosY() << ":"
+           << tile.getTileWidth() << ":" << tile.getTileHeight();
+    return tileID.str();
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index 2c10c5d24..ebdbdcbc7 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -19,6 +19,7 @@
 #include <Rectangle.hpp>
 #include <boost/optional.hpp>
 #include <list>
+#include <map>
 
 class DocumentBroker;
 
@@ -118,7 +119,9 @@ public:
     int getTileWidthInTwips() const { return _tileWidthTwips; }
     int getTileHeightInTwips() const { return _tileHeightTwips; }
 
-
+    /// This method updates internal data related to sent tiles (wireID and tiles-on-fly)
+    /// Call this method anytime when a new tile is sent to the client
+    void traceTileBySend(const TileDesc& tile);
 private:
 
     /// SocketHandler: disconnection event.
@@ -159,6 +162,11 @@ private:
     void handleTileInvalidation(const std::string& message,
                                 const std::shared_ptr<DocumentBroker>& docBroker);
 
+    /// Clear wireId map anytime when client visible area changes (visible area, zoom, part number)
+    void resetWireIdMap();
+
+    std::string generateTileID(const TileDesc& tile);
+
 private:
     std::weak_ptr<DocumentBroker> _docBroker;
 
@@ -197,12 +205,15 @@ private:
     int _tileWidthTwips;
     int _tileHeightTwips;
 
-    // Type of the docuemnt, extracter from status message
+    /// Client is using a text document?
     bool _isTextDocument;
 
     std::list<std::string> _tilesOnFly;
 
     boost::optional<std::list<TileDesc>> _requestedTiles;
+
+    /// Store wireID's of the sent tiles for the actual visible area
+    std::map<std::string, TileWireId> _oldWireIds;
 };
 
 
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 9089d72d0..53b554ea8 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -1385,7 +1385,6 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
         while(session->getTilesOnFlyCount() < tilesOnFlyUpperLimit && !requestedTiles.get().empty())
         {
             TileDesc& tile = *(requestedTiles.get().begin());
-            session->addTileOnFly(tile);
 
             // Satisfy as many tiles from the cache.
             std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(tile);
@@ -1412,6 +1411,7 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
                 cachedTile->read(output.data() + pos, size);
                 cachedTile->close();
 
+                session->traceTileBySend(tile);
                 session->sendBinaryFrame(output.data(), output.size());
             }
             else
diff --git a/wsd/TestStubs.cpp b/wsd/TestStubs.cpp
index 1fa332fd9..4dd80cbfd 100644
--- a/wsd/TestStubs.cpp
+++ b/wsd/TestStubs.cpp
@@ -15,6 +15,11 @@
 
 #include "DocumentBroker.hpp"
 
+#include "ClientSession.hpp"
+
 void DocumentBroker::assertCorrectThread() const {}
 
+
+void ClientSession::traceTileBySend(const TileDesc& /*tile*/) {}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/TileCache.cpp b/wsd/TileCache.cpp
index 78e598afa..629a273c1 100644
--- a/wsd/TileCache.cpp
+++ b/wsd/TileCache.cpp
@@ -189,7 +189,10 @@ void TileCache::saveTileAndNotify(const TileDesc& tile, const char *data, const
             auto& firstSubscriber = tileBeingRendered->_subscribers[0];
             auto firstSession = firstSubscriber.lock();
             if (firstSession)
+            {
+                firstSession->traceTileBySend(tile);
                 firstSession->enqueueSendMessage(payload);
+            }
 
             if (subscriberCount > 1)
             {
@@ -209,6 +212,7 @@ void TileCache::saveTileAndNotify(const TileDesc& tile, const char *data, const
                     auto session = subscriber.lock();
                     if (session)
                     {
+                        session->traceTileBySend(tile);
                         session->enqueueSendMessage(payload);
                     }
                 }
commit e717e7e2ebba683f4a4b58b5de79c23b93871d4e
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Thu Jul 5 14:40:28 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:09:19 2018 +0200

    Calculate TilesOnFly limit based on visible area
    
    Use 10 as a minimum value.
    
    Change-Id: I9442a427fd25e1a7a32c3d1d06aa34d2c4ca2472
    (cherry picked from commit e1b22eaac325a8ee2fe822f5cb37969b80f1fd13)

diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index 7cb62d858..2c10c5d24 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -114,6 +114,10 @@ public:
     void clearTilesOnFly();
     size_t getTilesOnFlyCount() const { return _tilesOnFly.size(); }
 
+    Util::Rectangle getVisibleArea() const { return _clientVisibleArea; }
+    int getTileWidthInTwips() const { return _tileWidthTwips; }
+    int getTileHeightInTwips() const { return _tileHeightTwips; }
+
 
 private:
 
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index b673fa3af..9089d72d0 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -41,7 +41,7 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 
-#define TILES_ON_FLY_UPPER_LIMIT 25
+#define TILES_ON_FLY_MIN_UPPER_LIMIT 10u
 
 using namespace LOOLProtocol;
 
@@ -1367,13 +1367,22 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
 {
     std::unique_lock<std::mutex> lock(_mutex);
 
+    // How many tiles we have on the visible area, set the upper limit accordingly
+    const unsigned tilesFitOnWidth = static_cast<unsigned>(std::ceil(static_cast<float>(session->getVisibleArea().getWidth()) /
+                                                                     static_cast<float>(session->getTileWidthInTwips())));
+    const unsigned tilesFitOnHeight = static_cast<unsigned>(std::ceil(static_cast<float>(session->getVisibleArea().getHeight()) /
+                                                                      static_cast<float>(session->getTileHeightInTwips())));
+    const unsigned tilesInVisArea = tilesFitOnWidth * tilesFitOnHeight;
+
+    const unsigned tilesOnFlyUpperLimit = std::max(TILES_ON_FLY_MIN_UPPER_LIMIT, tilesInVisArea);
+
     // All tiles were processed on client side what we sent last time, so we can send a new banch of tiles
     // which was invalidated / requested in the meantime
     boost::optional<std::list<TileDesc>>& requestedTiles = session->getRequestedTiles();
     if(requestedTiles != boost::none && !requestedTiles.get().empty())
     {
         std::vector<TileDesc> tilesNeedsRendering;
-        while(session->getTilesOnFlyCount() < TILES_ON_FLY_UPPER_LIMIT && !requestedTiles.get().empty())
+        while(session->getTilesOnFlyCount() < tilesOnFlyUpperLimit && !requestedTiles.get().empty())
         {
             TileDesc& tile = *(requestedTiles.get().begin());
             session->addTileOnFly(tile);
commit bf711e3146c255ad85969e3103e651d73e5a3adb
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Jul 10 14:05:36 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:05:45 2018 +0200

    Use an upper limit for number of tiles we push to the network
    
    I used number 25 as this limit. It's an approximate value. It's enough
    to handle the first 5-10 character input without waiting for tileprocessed
    messages. After the first 5-10 characters the tileprocessed messages are
    arriving continuously (same frequency as the typing speed), so for the later
    character inputs we always have some tileprocessed messages arrived so
    we can push new tiles to the network again.
    This 25 upper limit also seems enough to send all tiles when a whole page is
    requested (e.g. zoom os scroll).
    
    We store the requested tiles in a list, used as a queue. I don't use std::queue
    because sometimes we need to do deduplication (replace older versions of the
    same tile with a newer version).
    
    (cherry picked from commit 2fda5f7d925c26c8ec0de672842c40be73ee3bc8)
    
    Change-Id: I22ff3e35c8ded6001c7fc160abdc1f1b12ce3bae

diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 9349436bd..af15ac74a 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -77,8 +77,6 @@ bool ClientSession::_handleInput(const char *buffer, int length)
     const std::string firstLine = getFirstLine(buffer, length);
     const std::vector<std::string> tokens = LOOLProtocol::tokenize(firstLine.data(), firstLine.size());
 
-    checkTileRequestTimout();
-
     auto docBroker = getDocumentBroker();
     if (!docBroker)
     {
@@ -343,10 +341,6 @@ bool ClientSession::_handleInput(const char *buffer, int length)
         if(iter != _tilesOnFly.end())
             _tilesOnFly.erase(iter);
 
-        if(_tilesOnFly.empty())
-        {
-            _tileCounterStartTime = boost::none;
-        }
         docBroker->sendRequestedTiles(shared_from_this());
         return true;
     }
@@ -1032,25 +1026,17 @@ Authorization ClientSession::getAuthorization() const
     return Authorization();
 }
 
-void ClientSession::setTilesOnFly(boost::optional<TileCombined> tiles)
+void ClientSession::addTileOnFly(const TileDesc& tile)
 {
+    std::ostringstream tileID;
+    tileID << tile.getPart() << ":" << tile.getTilePosX() << ":" << tile.getTilePosY() << ":"
+           << tile.getTileWidth() << ":" << tile.getTileHeight();
+    _tilesOnFly.push_back(tileID.str());
+}
 
+void ClientSession::clearTilesOnFly()
+{
     _tilesOnFly.clear();
-    if(tiles == boost::none)
-    {
-        _tileCounterStartTime = boost::none;
-    }
-    else
-    {
-        for (auto& tile : tiles.get().getTiles())
-        {
-            std::ostringstream tileID;
-            tileID << tile.getPart() << ":" << tile.getTilePosX() << ":" << tile.getTilePosY() << ":"
-                   << tile.getTileWidth() << ":" << tile.getTileHeight();
-            _tilesOnFly.push_back(tileID.str());
-        }
-        _tileCounterStartTime = std::chrono::steady_clock::now();
-    }
 }
 
 void ClientSession::onDisconnect()
@@ -1184,17 +1170,4 @@ void ClientSession::handleTileInvalidation(const std::string& message,
     }
 }
 
-void ClientSession::checkTileRequestTimout()
-{
-    if(_tileCounterStartTime != boost::none)
-    {
-        const auto duration = std::chrono::steady_clock::now() - _tileCounterStartTime.get();
-        if( std::chrono::duration_cast<std::chrono::seconds>(duration).count() > 5)
-        {
-            LOG_WRN("Tile request timeout: server waits too long for tileprocessed messages.");
-            _tileCounterStartTime = boost::none;
-        }
-    }
-}
-
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index b0c67c04f..7cb62d858 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -18,7 +18,7 @@
 #include <Poco/URI.h>
 #include <Rectangle.hpp>
 #include <boost/optional.hpp>
-#include <vector>
+#include <list>
 
 class DocumentBroker;
 
@@ -108,10 +108,11 @@ public:
     /// Set WOPI fileinfo object
     void setWopiFileInfo(std::unique_ptr<WopiStorage::WOPIFileInfo>& wopiFileInfo) { _wopiFileInfo = std::move(wopiFileInfo); }
 
-    boost::optional<TileCombined>& getRequestedTiles() { return _requestedTiles; }
+    boost::optional<std::list<TileDesc>>& getRequestedTiles() { return _requestedTiles; }
 
-    const std::vector<std::string>& getTilesOnFly() const { return _tilesOnFly; }
-    void setTilesOnFly(boost::optional<TileCombined> tiles);
+    void addTileOnFly(const TileDesc& tile);
+    void clearTilesOnFly();
+    size_t getTilesOnFlyCount() const { return _tilesOnFly.size(); }
 
 
 private:
@@ -154,8 +155,6 @@ private:
     void handleTileInvalidation(const std::string& message,
                                 const std::shared_ptr<DocumentBroker>& docBroker);
 
-    void checkTileRequestTimout();
-
 private:
     std::weak_ptr<DocumentBroker> _docBroker;
 
@@ -197,10 +196,9 @@ private:
     // Type of the docuemnt, extracter from status message
     bool _isTextDocument;
 
-    std::vector<std::string> _tilesOnFly;
-    boost::optional<std::chrono::time_point<std::chrono::steady_clock>> _tileCounterStartTime;
+    std::list<std::string> _tilesOnFly;
 
-    boost::optional<TileCombined> _requestedTiles;
+    boost::optional<std::list<TileDesc>> _requestedTiles;
 };
 
 
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index b25fc1076..b673fa3af 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -41,6 +41,8 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 
+#define TILES_ON_FLY_UPPER_LIMIT 25
+
 using namespace LOOLProtocol;
 
 using Poco::JSON::Object;
@@ -1318,17 +1320,17 @@ void DocumentBroker::handleTileCombinedRequest(TileCombined& tileCombined,
     }
 
     // Accumulate tiles
-    boost::optional<TileCombined>& requestedTiles = session->getRequestedTiles();
+    boost::optional<std::list<TileDesc>>& requestedTiles = session->getRequestedTiles();
     if(requestedTiles == boost::none)
     {
-        requestedTiles = TileCombined::create(tileCombined.getTiles());
+        requestedTiles = std::list<TileDesc>(tileCombined.getTiles().begin(), tileCombined.getTiles().end());
     }
     // Drop duplicated tiles, but use newer version number
     else
     {
         for (const auto& newTile : tileCombined.getTiles())
         {
-            const TileDesc& firstOldTile = requestedTiles.get().getTiles()[0];
+            const TileDesc& firstOldTile = *(requestedTiles.get().begin());
             if(newTile.getPart() != firstOldTile.getPart() ||
                newTile.getWidth() != firstOldTile.getWidth() ||
                newTile.getHeight() != firstOldTile.getHeight() ||
@@ -1339,7 +1341,7 @@ void DocumentBroker::handleTileCombinedRequest(TileCombined& tileCombined,
             }
 
             bool tileFound = false;
-            for (auto& oldTile : requestedTiles.get().getTiles())
+            for (auto& oldTile : requestedTiles.get())
             {
                 if(oldTile.getTilePosX() == newTile.getTilePosX() &&
                    oldTile.getTilePosY() == newTile.getTilePosY() )
@@ -1352,7 +1354,7 @@ void DocumentBroker::handleTileCombinedRequest(TileCombined& tileCombined,
                 }
             }
             if(!tileFound)
-                requestedTiles.get().getTiles().push_back(newTile);
+                requestedTiles.get().push_back(newTile);
         }
     }
 
@@ -1367,15 +1369,16 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
 
     // All tiles were processed on client side what we sent last time, so we can send a new banch of tiles
     // which was invalidated / requested in the meantime
-    boost::optional<TileCombined>& requestedTiles = session->getRequestedTiles();
-    if(session->getTilesOnFly().empty() && requestedTiles != boost::none && !requestedTiles.get().getTiles().empty())
+    boost::optional<std::list<TileDesc>>& requestedTiles = session->getRequestedTiles();
+    if(requestedTiles != boost::none && !requestedTiles.get().empty())
     {
-        session->setTilesOnFly(requestedTiles.get());
-
-        // Satisfy as many tiles from the cache.
         std::vector<TileDesc> tilesNeedsRendering;
-        for (auto& tile : requestedTiles.get().getTiles())
+        while(session->getTilesOnFlyCount() < TILES_ON_FLY_UPPER_LIMIT && !requestedTiles.get().empty())
         {
+            TileDesc& tile = *(requestedTiles.get().begin());
+            session->addTileOnFly(tile);
+
+            // Satisfy as many tiles from the cache.
             std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(tile);
             if (cachedTile)
             {
@@ -1416,6 +1419,7 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
                 }
                 tileCache().subscribeToTileRendering(tile, session);
             }
+            requestedTiles.get().pop_front();
         }
 
         // Send rendering request for those tiles which were not prerendered
@@ -1426,10 +1430,8 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
             // Forward to child to render.
             const std::string req = newTileCombined.serialize("tilecombine");
             LOG_DBG("Some of the tiles were not prerendered. Sending residual tilecombine: " << req);
-            LOG_DBG("Sending residual tilecombine: " << req);
             _childProcess->sendTextFrame(req);
         }
-        requestedTiles = boost::none;
     }
 }
 
@@ -1438,7 +1440,7 @@ void DocumentBroker::cancelTileRequests(const std::shared_ptr<ClientSession>& se
     std::unique_lock<std::mutex> lock(_mutex);
 
     // Clear tile requests
-    session->setTilesOnFly(boost::none);
+    session->clearTilesOnFly();
     session->getRequestedTiles() = boost::none;
 
     const auto canceltiles = tileCache().cancelTiles(session);
commit 7e1759be17c175e5516f6413049aa35e040bc9d1
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Jun 19 16:41:02 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:04:39 2018 +0200

    Check whether tile rendering request was already sent
    
    Change-Id: Iceb559106dcd95d6ff7db67df76cdfb04f9fb7e0
    (cherry picked from commit 4e2b50dc0fb3af8016dd14ca90fa563e8f0146c4)

diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index ee781e559..b25fc1076 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -1408,6 +1408,9 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
                 if(tile.getVersion() == -1) // Rendering of this tile was not requested yet
                 {
                     tile.setVersion(++_tileVersion);
+                }
+                if(!tileCache().hasTileBeingRendered(tile))
+                {
                     tilesNeedsRendering.push_back(tile);
                     _debugRenderedTileCount++;
                 }
diff --git a/wsd/TileCache.cpp b/wsd/TileCache.cpp
index 11896e2f5..78e598afa 100644
--- a/wsd/TileCache.cpp
+++ b/wsd/TileCache.cpp
@@ -129,6 +129,11 @@ void TileCache::forgetTileBeingRendered(const TileDesc& tile)
     _tilesBeingRendered.erase(cachedName);
 }
 
+bool TileCache::hasTileBeingRendered(const TileDesc& tile)
+{
+    return findTileBeingRendered(tile) != nullptr;
+}
+
 std::unique_ptr<std::fstream> TileCache::lookupTile(const TileDesc& tile)
 {
     const std::string fileName = _cacheDir + "/" + cacheFileName(tile);
diff --git a/wsd/TileCache.hpp b/wsd/TileCache.hpp
index d8c48eaa4..560b66ccc 100644
--- a/wsd/TileCache.hpp
+++ b/wsd/TileCache.hpp
@@ -80,10 +80,13 @@ public:
     void saveLastModified(const Poco::Timestamp& timestamp);
 
     void forgetTileBeingRendered(const TileDesc& tile);
+    bool hasTileBeingRendered(const TileDesc& tile);
 
     void setThreadOwner(const std::thread::id &id) { _owner = id; }
     void assertCorrectThread();
 
+
+
 private:
     void invalidateTiles(int part, int x, int y, int width, int height);
 
commit 006453adeaee008d9f5610cd5054a387a2860b26
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Jun 19 16:15:37 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 12:01:58 2018 +0200

    Handle part number a bit more robust in case of Writer
    
    (cherry picked from commit 6c4e4440e89a9a4d8e2747a280c8e96c9d142069)
    
    Change-Id: I7390f1c5f4289be67deacf3540068c040b230584

diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 555df9954..9349436bd 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -49,7 +49,8 @@ ClientSession::ClientSession(const std::string& id,
     _tileWidthPixel(0),
     _tileHeightPixel(0),
     _tileWidthTwips(0),
-    _tileHeightTwips(0)
+    _tileHeightTwips(0),
+    _isTextDocument(false)
 {
     const size_t curConnections = ++LOOLWSD::NumConnections;
     LOG_INF("ClientSession ctor [" << getName() << "], current number of connections: " << curConnections);
@@ -292,17 +293,20 @@ bool ClientSession::_handleInput(const char *buffer, int length)
     }
     else if (tokens[0] == "setclientpart")
     {
-        int temp;
-        if (tokens.size() != 2 ||
-            !getTokenInteger(tokens[1], "part", temp))
+        if(!_isTextDocument)
         {
-            sendTextFrame("error: cmd=setclientpart kind=syntax");
-            return false;
-        }
-        else
-        {
-            _clientSelectedPart = temp;
-            return forwardToChild(std::string(buffer, length), docBroker);
+            int temp;
+            if (tokens.size() != 2 ||
+                !getTokenInteger(tokens[1], "part", temp))
+            {
+                sendTextFrame("error: cmd=setclientpart kind=syntax");
+                return false;
+            }
+            else
+            {
+                _clientSelectedPart = temp;
+                return forwardToChild(std::string(buffer, length), docBroker);
+            }
         }
     }
     else if (tokens[0] == "clientzoom")
@@ -725,17 +729,20 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
     }
     else if (tokens[0] == "setpart:" && tokens.size() == 2)
     {
-        int setPart;
-        if(getTokenInteger(tokens[1], "part", setPart))
+        if(!_isTextDocument)
         {
-            _clientSelectedPart = setPart;
-        }
-        else if (stringToInteger(tokens[1], setPart))
-        {
-            _clientSelectedPart = setPart;
-        }
-        else
-            return false;
+            int setPart;
+            if(getTokenInteger(tokens[1], "part", setPart))
+            {
+                _clientSelectedPart = setPart;
+            }
+            else if (stringToInteger(tokens[1], setPart))
+            {
+                _clientSelectedPart = setPart;
+            }
+            else
+                return false;
+         }
     }
     else if (tokens.size() == 3 && tokens[0] == "saveas:")
     {
@@ -900,7 +907,7 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
                 std::string docType;
                 if(getTokenString(token, "type", docType))
                 {
-                    _docType = docType;
+                    _isTextDocument = docType.find("text") != std::string::npos;
                 }
             }
 
@@ -1130,13 +1137,11 @@ void ClientSession::handleTileInvalidation(const std::string& message,
 {
     docBroker->invalidateTiles(message);
 
-    bool bIsTextDocument = _docType.find("text") != std::string::npos;
-
     // Skip requesting new tiles if we don't have client visible area data yet.
     if(!_clientVisibleArea.hasSurface() ||
        _tileWidthPixel == 0 || _tileHeightPixel == 0 ||
        _tileWidthTwips == 0 || _tileHeightTwips == 0 ||
-       (_clientSelectedPart == -1 && !bIsTextDocument))
+       (_clientSelectedPart == -1 && !_isTextDocument))
     {
         return;
     }
@@ -1145,14 +1150,11 @@ void ClientSession::handleTileInvalidation(const std::string& message,
     int part = result.first;
     Util::Rectangle& invalidateRect = result.second;
 
-    if(bIsTextDocument) // For Writer we don't have separate parts
-        part = 0;
-
-    if(part == -1) // If no part is specifed we use the part used by the client
+    if( part == -1 ) // If no part is specifed we use the part used by the client
         part = _clientSelectedPart;
 
     std::vector<TileDesc> invalidTiles;
-    if(part == _clientSelectedPart || bIsTextDocument)
+    if(part == _clientSelectedPart || _isTextDocument)
     {
         Util::Rectangle intersection;
         intersection._x1 = std::max(invalidateRect._x1, _clientVisibleArea._x1);
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index 899e6f6f6..b0c67c04f 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -195,7 +195,7 @@ private:
     int _tileHeightTwips;
 
     // Type of the docuemnt, extracter from status message
-    std::string _docType;
+    bool _isTextDocument;
 
     std::vector<std::string> _tilesOnFly;
     boost::optional<std::chrono::time_point<std::chrono::steady_clock>> _tileCounterStartTime;
commit 139e180b77db9ae7dc6aba29aa001370e72ca312
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Jul 10 14:12:11 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 11:59:18 2018 +0200

    In Impress setpart message's syntax is a bit different.
    
    Two alternative sytnax:
    "setpart part=1"
    "setpart 1"
    
    Change-Id: I42683ca46d642d56cfc3dcc52a10d69a3f00462b
    (cherry picked from commit 7428c46efe1dd2f948ca9754ab5dc719ff991f4b)

diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index f6087c55f..555df9954 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -727,7 +727,13 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
     {
         int setPart;
         if(getTokenInteger(tokens[1], "part", setPart))
+        {
             _clientSelectedPart = setPart;
+        }
+        else if (stringToInteger(tokens[1], setPart))
+        {
+            _clientSelectedPart = setPart;
+        }
         else
             return false;
     }
commit 5a2d185fc6bfb30133a341b574f3f73547a2cc86
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Fri Jun 15 16:33:28 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 11:58:01 2018 +0200

    Send new clientvisiblearea and clientzoom messages
    
    when something is actually changed.
    
    (cherry picked from commit 1aa8f3b17bc7d6489d0c8dc45cec4acac4938871)
    
    Change-Id: I56983f5700cb9cbd0b660155a4dd0a2396b22e2a

diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index 8f1b47242..a18f7e84f 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -315,11 +315,7 @@ L.CalcTileLayer = L.TileLayer.extend({
 	},
 
 	_onZoomRowColumns: function () {
-		this._updateClientZoom();
-		if (this._clientZoom) {
-			this._map._socket.sendMessage('clientzoom ' + this._clientZoom);
-			this._clientZoom = null;
-		}
+		this._sendClientZoom();
 		// TODO: test it!
 		this._map.fire('updaterowcolumnheaders');
 	},
diff --git a/loleaflet/src/layer/tile/GridLayer.js b/loleaflet/src/layer/tile/GridLayer.js
index ad39ae8db..7f252d1a2 100644
--- a/loleaflet/src/layer/tile/GridLayer.js
+++ b/loleaflet/src/layer/tile/GridLayer.js
@@ -24,6 +24,9 @@ L.GridLayer = L.Layer.extend({
 
 	initialize: function (options) {
 		options = L.setOptions(this, options);
+
+		this._clientZoom = '';
+		this._clientVisibleArea = '';
 	},
 
 	onAdd: function () {
@@ -556,10 +559,8 @@ L.GridLayer = L.Layer.extend({
 			this._level.el.appendChild(fragment);
 		}
 
-		this._invalidateClientVisibleArea();
 		this._sendClientVisibleArea();
 
-		this._updateClientZoom();
 		this._sendClientZoom();
 	},
 
@@ -690,6 +691,34 @@ L.GridLayer = L.Layer.extend({
 		}
 	},
 
+	_sendClientVisibleArea: function () {
+		var visibleTopLeft = this._latLngToTwips(this._map.getBounds().getNorthWest());
+		var visibleBottomRight = this._latLngToTwips(this._map.getBounds().getSouthEast());
+		var visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight);
+		var size = new L.Point(visibleArea.getSize().x, visibleArea.getSize().y);
+		var newClientVisibleArea = 'clientvisiblearea x=' + Math.round(visibleTopLeft.x) + ' y=' + Math.round(visibleTopLeft.y) +
+			' width=' + Math.round(size.x) + ' height=' + Math.round(size.y);
+
+		if (this._clientVisibleArea !== newClientVisibleArea) {
+			// Visible area is dirty, update it on the server
+			this._clientVisibleArea = newClientVisibleArea
+			this._map._socket.sendMessage(this._clientVisibleArea);
+		}
+	},
+
+	_sendClientZoom: function () {
+		var newClientZoom = 'tilepixelwidth=' + this._tileWidthPx + ' ' +
+			'tilepixelheight=' + this._tileHeightPx + ' ' +
+			'tiletwipwidth=' + this._tileWidthTwips + ' ' +
+			'tiletwipheight=' + this._tileHeightTwips;
+
+		if (this._clientZoom !== newClientZoom) {
+			// the zoom level has changed
+			this._clientZoom = newClientZoom;
+			this._map._socket.sendMessage('clientzoom ' + this._clientZoom);
+		}
+	},
+
 	_cancelTiles: function() {
 		this._map._socket.sendMessage('canceltiles');
 		for (var key in this._tiles) {
@@ -730,39 +759,6 @@ L.GridLayer = L.Layer.extend({
 		this._emptyTilesCount = 0;
 	},
 
-	_invalidateClientVisibleArea: function() {
-		if (this._debug) {
-			this._debugInfo.clearLayers();
-			for (var key in this._tiles) {
-				this._tiles[key]._debugPopup = null;
-				this._tiles[key]._debugTile = null;
-			}
-		}
-		this._clientVisibleArea = true;
-	},
-
-	_sendClientVisibleArea: function () {
-		if (this._clientVisibleArea) {
-			// Visible area is dirty, update it on the server.
-			var visibleTopLeft = this._latLngToTwips(this._map.getBounds().getNorthWest());
-			var visibleBottomRight = this._latLngToTwips(this._map.getBounds().getSouthEast());
-			var visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight);
-			var size = new L.Point(visibleArea.getSize().x, visibleArea.getSize().y);
-			var payload = 'clientvisiblearea x=' + Math.round(visibleTopLeft.x) + ' y=' + Math.round(visibleTopLeft.y) +
-				' width=' + Math.round(size.x) + ' height=' + Math.round(size.y);
-			this._map._socket.sendMessage(payload);
-			this._clientVisibleArea = false;
-		}
-	},
-
-	_sendClientZoom: function () {
-		if (this._clientZoom) {
-			// the zoom level has changed
-			this._map._socket.sendMessage('clientzoom ' + this._clientZoom);
-			this._clientZoom = null;
-		}
-	},
-
 	_isValidTile: function (coords) {
 		if (coords.x < 0 || coords.y < 0) {
 			return false;
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 3e06e4772..70a1bfa93 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -137,15 +137,11 @@ L.TileLayer = L.GridLayer.extend({
 		this._msgQueue = [];
 		this._toolbarCommandValues = {};
 		this._previewInvalidations = [];
-		this._updateClientZoom();
 
 		this._followThis = -1;
 		this._editorId = -1;
 		this._followUser = false;
 		this._followEditor = false;
-
-		// Mark visible area as dirty by default.
-		this._invalidateClientVisibleArea();
 	},
 
 	onAdd: function (map) {
@@ -247,9 +243,7 @@ L.TileLayer = L.GridLayer.extend({
 		if (this._docType === 'spreadsheet') {
 			map.on('zoomend', this._onCellCursorShift, this);
 		}
-		map.on('zoomend', this._updateClientZoom, this);
 		map.on('zoomend', L.bind(this.eachView, this, this._viewCursors, this._onUpdateViewCursor, this, false));
-		map.on('resize zoomend', this._invalidateClientVisibleArea, this);
 		map.on('dragstart', this._onDragStart, this);
 		map.on('requestloksession', this._onRequestLOKSession, this);
 		map.on('error', this._mapOnError, this);
@@ -480,7 +474,6 @@ L.TileLayer = L.GridLayer.extend({
 	},
 
 	toggleTileDebugMode: function() {
-		this._invalidateClientVisibleArea();
 		this._debug = !this._debug;
 		if (!this._debug) {
 			map.removeLayer(this._debugInfo);
@@ -2174,13 +2167,6 @@ L.TileLayer = L.GridLayer.extend({
 		this._previewInvalidations = [];
 	},
 
-	_updateClientZoom: function () {
-		this._clientZoom = 'tilepixelwidth=' + this._tileWidthPx + ' ' +
-			'tilepixelheight=' + this._tileHeightPx + ' ' +
-			'tiletwipwidth=' + this._tileWidthTwips + ' ' +
-			'tiletwipheight=' + this._tileHeightTwips;
-	},
-
 	_debugGetTimeArray: function() {
 		return {count: 0, ms: 0, best: Number.MAX_SAFE_INTEGER, worst: 0, date: 0};
 	},
commit 27f0f665a0ec13acc6817ec28746d0e53dc2d1ba
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Sat Jun 16 14:22:01 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 11:57:09 2018 +0200

    Need to extract the initial part id from status message
    
    Change-Id: Ia0651d93fedb71d3ca1e24d0356ac179e95e907e
    (cherry picked from commit 29df46219c1fcabc2dad74699127a8bf57acb8a2)

diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 341e154c2..f6087c55f 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -881,11 +881,21 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt
             setViewLoaded();
             docBroker->setLoaded();
 
-            const std::string stringMsg(buffer, length);
-            const size_t index = stringMsg.find("type=") + 5;
-            if (index != std::string::npos)
+            for(auto &token : tokens)
             {
-                _docType = stringMsg.substr(index, stringMsg.find_first_of(' ', index) - index);
+                // Need to get the initial part id from status message
+                int part = -1;
+                if(getTokenInteger(token, "current", part))
+                {
+                    _clientSelectedPart = part;
+                }
+
+                // Get document type too
+                std::string docType;
+                if(getTokenString(token, "type", docType))
+                {
+                    _docType = docType;
+                }
             }
 
             // Forward the status response to the client.
commit 11388b3f64658ba6cbe5a90f1720b91ac0db8c31
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Wed Jun 13 15:04:09 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 11:55:08 2018 +0200

    Store sent tiles's id instead of using a simple counter
    
    Change-Id: I8cbf84923a53fb6b294bd4039eb7382326f8c445
    (cherry picked from commit 85f96bc281f037087fb226b018c40b8410d353d5)

diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 5922f2568..3e06e4772 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -1339,7 +1339,8 @@ L.TileLayer = L.GridLayer.extend({
 		L.Log.log(textMsg, L.INCOMING, key);
 
 		// Send acknowledgment, that the tile message arrived
-		this._map._socket.sendMessage('tileprocessed tile= ' + key);
+		var tileID = command.part + ':' + command.x + ':' + command.y + ':' + command.tileWidth + ':' + command.tileHeight;
+		this._map._socket.sendMessage('tileprocessed tile=' + tileID);
 	},
 
 	_tileOnLoad: function (done, tile) {
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index de99f9ce1..341e154c2 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -12,6 +12,7 @@
 #include "ClientSession.hpp"
 
 #include <fstream>
+#include <sstream>
 
 #include <Poco/Net/HTTPResponse.h>
 #include <Poco/StringTokenizer.h>
@@ -48,8 +49,7 @@ ClientSession::ClientSession(const std::string& id,
     _tileWidthPixel(0),
     _tileHeightPixel(0),
     _tileWidthTwips(0),
-    _tileHeightTwips(0),
-    _tilesOnFly(0)
+    _tileHeightTwips(0)
 {
     const size_t curConnections = ++LOOLWSD::NumConnections;
     LOG_INF("ClientSession ctor [" << getName() << "], current number of connections: " << curConnections);
@@ -328,13 +328,20 @@ bool ClientSession::_handleInput(const char *buffer, int length)
     }
     else if (tokens[0] == "tileprocessed")
     {
-        if(_tilesOnFly > 0) // canceltiles message can zero this value
+        std::string tileID;
+        if (tokens.size() != 2 ||
+            !getTokenString(tokens[1], "tile", tileID))
         {
-            --_tilesOnFly;
-            if(_tilesOnFly == 0)
-            {
-                _tileCounterStartTime = boost::none;
-            }
+            sendTextFrame("error: cmd=tileprocessed kind=syntax");
+            return false;
+        }
+        auto iter = std::find(_tilesOnFly.begin(), _tilesOnFly.end(), tileID);
+        if(iter != _tilesOnFly.end())
+            _tilesOnFly.erase(iter);
+
+        if(_tilesOnFly.empty())
+        {
+            _tileCounterStartTime = boost::none;
         }
         docBroker->sendRequestedTiles(shared_from_this());
         return true;
@@ -1002,15 +1009,23 @@ Authorization ClientSession::getAuthorization() const
     return Authorization();
 }
 
-void ClientSession::setTilesOnFly(int tilesOnFly)
+void ClientSession::setTilesOnFly(boost::optional<TileCombined> tiles)
 {
-    _tilesOnFly = tilesOnFly;
-    if(tilesOnFly == 0)
+
+    _tilesOnFly.clear();
+    if(tiles == boost::none)
     {
         _tileCounterStartTime = boost::none;
     }
     else
     {
+        for (auto& tile : tiles.get().getTiles())
+        {
+            std::ostringstream tileID;
+            tileID << tile.getPart() << ":" << tile.getTilePosX() << ":" << tile.getTilePosY() << ":"
+                   << tile.getTileWidth() << ":" << tile.getTileHeight();
+            _tilesOnFly.push_back(tileID.str());
+        }
         _tileCounterStartTime = std::chrono::steady_clock::now();
     }
 }
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index 2609d5f9a..899e6f6f6 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -18,6 +18,7 @@
 #include <Poco/URI.h>
 #include <Rectangle.hpp>
 #include <boost/optional.hpp>
+#include <vector>
 
 class DocumentBroker;
 
@@ -109,8 +110,8 @@ public:
 
     boost::optional<TileCombined>& getRequestedTiles() { return _requestedTiles; }
 
-    int getTilesOnFly() const { return _tilesOnFly; }
-    void setTilesOnFly(int tilesOnFly);
+    const std::vector<std::string>& getTilesOnFly() const { return _tilesOnFly; }
+    void setTilesOnFly(boost::optional<TileCombined> tiles);
 
 
 private:
@@ -196,7 +197,7 @@ private:
     // Type of the docuemnt, extracter from status message
     std::string _docType;
 
-    int _tilesOnFly;
+    std::vector<std::string> _tilesOnFly;
     boost::optional<std::chrono::time_point<std::chrono::steady_clock>> _tileCounterStartTime;
 
     boost::optional<TileCombined> _requestedTiles;
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index bff94b690..ee781e559 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -1363,15 +1363,14 @@ void DocumentBroker::handleTileCombinedRequest(TileCombined& tileCombined,
 
 void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& session)
 {
-    assert(session->getTilesOnFly() >= 0);
     std::unique_lock<std::mutex> lock(_mutex);
 
     // All tiles were processed on client side what we sent last time, so we can send a new banch of tiles
     // which was invalidated / requested in the meantime
     boost::optional<TileCombined>& requestedTiles = session->getRequestedTiles();
-    if(session->getTilesOnFly() == 0 && requestedTiles != boost::none && !requestedTiles.get().getTiles().empty())
+    if(session->getTilesOnFly().empty() && requestedTiles != boost::none && !requestedTiles.get().getTiles().empty())
     {
-        session->setTilesOnFly(requestedTiles.get().getTiles().size());
+        session->setTilesOnFly(requestedTiles.get());
 
         // Satisfy as many tiles from the cache.
         std::vector<TileDesc> tilesNeedsRendering;
@@ -1436,7 +1435,7 @@ void DocumentBroker::cancelTileRequests(const std::shared_ptr<ClientSession>& se
     std::unique_lock<std::mutex> lock(_mutex);
 
     // Clear tile requests
-    session->setTilesOnFly(0);
+    session->setTilesOnFly(boost::none);
     session->getRequestedTiles() = boost::none;
 
     const auto canceltiles = tileCache().cancelTiles(session);
diff --git a/wsd/protocol.txt b/wsd/protocol.txt
index 056b3fc00..159bf1516 100644
--- a/wsd/protocol.txt
+++ b/wsd/protocol.txt
@@ -34,7 +34,7 @@ canceltiles
 tileprocessed tile=<tileid>
 
     Previously sent tile (server -> client) arrived and processed by the client.
-    Tileid has the next stucture : <tile x coord>:<tile y coord>:<zoom level>:<selected part>
+    Tileid has the next stucture : <selected part>:<tile x coord>:<tile y coord>:<tile width in twips>:<tile height in twips>
 
 downloadas name=<fileName> id=<id> format=<document format> options=<SkipImages, etc>
 
commit 57133ef5081f3e57271e9184da4e56035146e4da
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Jun 12 14:51:38 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 11:54:53 2018 +0200

    We might need to rerequest tile rendering when we are at sending them
    
    Change-Id: I0551e51c5f5023931dad13435b4ac3517fc48931
    (cherry picked from commit 464dd72e1c3471a7e22b5d9cb4c7437e407fa4ba)

diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 93db73c7e..bff94b690 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -1306,7 +1306,7 @@ void DocumentBroker::handleTileCombinedRequest(TileCombined& tileCombined,
         }
     }
 
-    // Send rendering request
+    // Send rendering request, prerender before we actually send the tiles
     if (!tilesNeedsRendering.empty())
     {
         auto newTileCombined = TileCombined::create(tilesNeedsRendering);
@@ -1374,6 +1374,7 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
         session->setTilesOnFly(requestedTiles.get().getTiles().size());
 
         // Satisfy as many tiles from the cache.
+        std::vector<TileDesc> tilesNeedsRendering;
         for (auto& tile : requestedTiles.get().getTiles())
         {
             std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(tile);
@@ -1404,10 +1405,28 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se
             }
             else
             {
-                // Not cached, needs rendering. Rendering request was already sent
+                // Not cached, needs rendering.
+                if(tile.getVersion() == -1) // Rendering of this tile was not requested yet
+                {
+                    tile.setVersion(++_tileVersion);
+                    tilesNeedsRendering.push_back(tile);
+                    _debugRenderedTileCount++;
+                }
                 tileCache().subscribeToTileRendering(tile, session);
             }
         }
+
+        // Send rendering request for those tiles which were not prerendered
+        if (!tilesNeedsRendering.empty())
+        {
+            TileCombined newTileCombined = TileCombined::create(tilesNeedsRendering);
+
+            // Forward to child to render.
+            const std::string req = newTileCombined.serialize("tilecombine");
+            LOG_DBG("Some of the tiles were not prerendered. Sending residual tilecombine: " << req);
+            LOG_DBG("Sending residual tilecombine: " << req);
+            _childProcess->sendTextFrame(req);
+        }
         requestedTiles = boost::none;
     }
 }
commit 7345e15da426bd2f0ac4106401d82d22811f31d6
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Mon Jun 11 16:26:09 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 11:54:37 2018 +0200

    Reduce code deduplication
    
    We can request tilecombine even if client needs actually one tile only.
    
    Change-Id: Id897f219885be4cb93635d727d4ee871a4b55cb7
    (cherry picked from commit 30f4cafd3758c3003cbcd44f5923336aa6be0af4)

diff --git a/loleaflet/src/layer/tile/GridLayer.js b/loleaflet/src/layer/tile/GridLayer.js
index b485cfb15..ad39ae8db 100644
--- a/loleaflet/src/layer/tile/GridLayer.js
+++ b/loleaflet/src/layer/tile/GridLayer.js
@@ -971,53 +971,33 @@ L.GridLayer = L.Layer.extend({
 		var twips, msg;
 		for (var r = 0; r < rectangles.length; ++r) {
 			rectQueue = rectangles[r];
-
-			if (rectQueue.length === 1) {
-				// only one tile here
-				coords = rectQueue[0];
-				key = this._tileCoordsToKey(coords);
-
+			var tilePositionsX = '';
+			var tilePositionsY = '';
+			for (i = 0; i < rectQueue.length; i++) {
+				coords = rectQueue[i];
 				twips = this._coordsToTwips(coords);
-				msg = 'tile ' +
-					'part=' + coords.part + ' ' +
-					'width=' + this._tileWidthPx + ' ' +
-					'height=' + this._tileHeightPx + ' ' +
-					'tileposx=' + twips.x + ' '	+
-					'tileposy=' + twips.y + ' ' +
-					'tilewidth=' + this._tileWidthTwips + ' ' +
-					'tileheight=' + this._tileHeightTwips;
-				this._map._socket.sendMessage(msg, key);
-			}
-			else {
-				// more tiles, use tilecombine
-				var tilePositionsX = '';
-				var tilePositionsY = '';
-				for (i = 0; i < rectQueue.length; i++) {
-					coords = rectQueue[i];
-					twips = this._coordsToTwips(coords);
-
-					if (tilePositionsX !== '') {
-						tilePositionsX += ',';
-					}
-					tilePositionsX += twips.x;
 
-					if (tilePositionsY !== '') {
-						tilePositionsY += ',';
-					}
-					tilePositionsY += twips.y;
+				if (tilePositionsX !== '') {
+					tilePositionsX += ',';
 				}
+				tilePositionsX += twips.x;
 
-				twips = this._coordsToTwips(coords);
-				msg = 'tilecombine ' +
-					'part=' + coords.part + ' ' +
-					'width=' + this._tileWidthPx + ' ' +
-					'height=' + this._tileHeightPx + ' ' +
-					'tileposx=' + tilePositionsX + ' '	+
-					'tileposy=' + tilePositionsY + ' ' +
-					'tilewidth=' + this._tileWidthTwips + ' ' +
-					'tileheight=' + this._tileHeightTwips;
-				this._map._socket.sendMessage(msg, '');
+				if (tilePositionsY !== '') {
+					tilePositionsY += ',';
+				}
+				tilePositionsY += twips.y;
 			}
+
+			twips = this._coordsToTwips(coords);
+			msg = 'tilecombine ' +
+				'part=' + coords.part + ' ' +
+				'width=' + this._tileWidthPx + ' ' +
+				'height=' + this._tileHeightPx + ' ' +
+				'tileposx=' + tilePositionsX + ' '	+
+				'tileposy=' + tilePositionsY + ' ' +
+				'tilewidth=' + this._tileWidthTwips + ' ' +
+				'tileheight=' + this._tileHeightTwips;
+			this._map._socket.sendMessage(msg, '');
 		}
 	},
 
commit cc2ce747609ce381101c83430d5e9ee4c898f8ca
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Sat Jun 9 21:33:44 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 11:53:52 2018 +0200

    Send the right visible area to the server
    
    Change-Id: I036dfaa566fa7d4e370386d839bd2397cbf929f8
    (cherry picked from commit 1f2982cdc53236b0312438a1bd812d8d203ac4fb)

diff --git a/loleaflet/src/layer/tile/GridLayer.js b/loleaflet/src/layer/tile/GridLayer.js
index 1fc666a7f..b485cfb15 100644
--- a/loleaflet/src/layer/tile/GridLayer.js
+++ b/loleaflet/src/layer/tile/GridLayer.js
@@ -555,6 +555,12 @@ L.GridLayer = L.Layer.extend({
 			this._addTiles(queue, fragment);
 			this._level.el.appendChild(fragment);
 		}
+
+		this._invalidateClientVisibleArea();
+		this._sendClientVisibleArea();
+
+		this._updateClientZoom();
+		this._sendClientZoom();
 	},
 
 	_updateOnChangePart: function () {
@@ -724,6 +730,39 @@ L.GridLayer = L.Layer.extend({
 		this._emptyTilesCount = 0;
 	},
 
+	_invalidateClientVisibleArea: function() {
+		if (this._debug) {
+			this._debugInfo.clearLayers();
+			for (var key in this._tiles) {
+				this._tiles[key]._debugPopup = null;
+				this._tiles[key]._debugTile = null;
+			}
+		}
+		this._clientVisibleArea = true;
+	},
+
+	_sendClientVisibleArea: function () {
+		if (this._clientVisibleArea) {
+			// Visible area is dirty, update it on the server.
+			var visibleTopLeft = this._latLngToTwips(this._map.getBounds().getNorthWest());
+			var visibleBottomRight = this._latLngToTwips(this._map.getBounds().getSouthEast());
+			var visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight);
+			var size = new L.Point(visibleArea.getSize().x, visibleArea.getSize().y);
+			var payload = 'clientvisiblearea x=' + Math.round(visibleTopLeft.x) + ' y=' + Math.round(visibleTopLeft.y) +
+				' width=' + Math.round(size.x) + ' height=' + Math.round(size.y);
+			this._map._socket.sendMessage(payload);
+			this._clientVisibleArea = false;
+		}
+	},
+
+	_sendClientZoom: function () {
+		if (this._clientZoom) {
+			// the zoom level has changed
+			this._map._socket.sendMessage('clientzoom ' + this._clientZoom);
+			this._clientZoom = null;
+		}
+	},
+
 	_isValidTile: function (coords) {
 		if (coords.x < 0 || coords.y < 0) {
 			return false;
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 56fe4504b..5922f2568 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -137,10 +137,7 @@ L.TileLayer = L.GridLayer.extend({
 		this._msgQueue = [];
 		this._toolbarCommandValues = {};
 		this._previewInvalidations = [];
-		this._clientZoom = 'tilepixelwidth=' + this._tileWidthPx + ' ' +
-			'tilepixelheight=' + this._tileHeightPx + ' ' +
-			'tiletwipwidth=' + this.options.tileWidthTwips + ' ' +
-			'tiletwipheight=' + this.options.tileHeightTwips;
+		this._updateClientZoom();
 
 		this._followThis = -1;
 		this._editorId = -1;
@@ -1383,22 +1380,10 @@ L.TileLayer = L.GridLayer.extend({
 	},
 
 	_postMouseEvent: function(type, x, y, count, buttons, modifier) {
-		if (this._clientZoom) {
-			// the zoom level has changed
-			this._map._socket.sendMessage('clientzoom ' + this._clientZoom);
-			this._clientZoom = null;
-		}
 
-		if (this._clientVisibleArea) {
-			// Visible area is dirty, update it on the server.
-			var visibleArea = this._map._container.getBoundingClientRect();
-			var pos = this._pixelsToTwips(new L.Point(visibleArea.left, visibleArea.top));
-			var size = this._pixelsToTwips(new L.Point(visibleArea.width, visibleArea.height));
-			var payload = 'clientvisiblearea x=' + Math.round(pos.x) + ' y=' + Math.round(pos.y) +
-				' width=' + Math.round(size.x) + ' height=' + Math.round(size.y);
-			this._map._socket.sendMessage(payload);
-			this._clientVisibleArea = false;
-		}
+		this._sendClientZoom();
+
+		this._sendClientVisibleArea();
 
 		this._map._socket.sendMessage('mouse type=' + type +
 				' x=' + x + ' y=' + y + ' count=' + count +
@@ -1425,21 +1410,11 @@ L.TileLayer = L.GridLayer.extend({
 				this._cellCursorOnPgDn = new L.LatLngBounds(this._prevCellCursor.getSouthWest(), this._prevCellCursor.getNorthEast());
 			}
 		}
-		if (this._clientZoom) {
-			// the zoom level has changed
-			this._map._socket.sendMessage('clientzoom ' + this._clientZoom);
-			this._clientZoom = null;
-		}
-		if (this._clientVisibleArea) {
-			// Visible area is dirty, update it on the server.
-			var visibleArea = this._map._container.getBoundingClientRect();
-			var pos = this._pixelsToTwips(new L.Point(visibleArea.left, visibleArea.top));
-			var size = this._pixelsToTwips(new L.Point(visibleArea.width, visibleArea.height));
-			var payload = 'clientvisiblearea x=' + Math.round(pos.x) + ' y=' + Math.round(pos.y) +
-				' width=' + Math.round(size.x) + ' height=' + Math.round(size.y);
-			this._map._socket.sendMessage(payload);
-			this._clientVisibleArea = false;
-		}
+
+		this._sendClientZoom();
+
+		this._sendClientVisibleArea();
+
 		this._map._socket.sendMessage('key type=' + type +
 				' char=' + charcode + ' key=' + keycode);
 	},
@@ -2205,17 +2180,6 @@ L.TileLayer = L.GridLayer.extend({
 			'tiletwipheight=' + this._tileHeightTwips;
 	},
 
-	_invalidateClientVisibleArea: function() {
-		if (this._debug) {
-			this._debugInfo.clearLayers();
-			for (var key in this._tiles) {
-				this._tiles[key]._debugPopup = null;
-				this._tiles[key]._debugTile = null;
-			}
-		}
-		this._clientVisibleArea = true;
-	},
-
 	_debugGetTimeArray: function() {
 		return {count: 0, ms: 0, best: Number.MAX_SAFE_INTEGER, worst: 0, date: 0};
 	},
commit be9bdee279eb4072a6398b9aff91e3966bfc7abd
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Jul 10 14:10:28 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 11:51:10 2018 +0200

    Add a timeout for tileprocessed message handling
    
    For debug purposes.
    
    (cherry picked from commit a9c5ea9022e78969118f23d5784a04df3f3b1e36)
    
    Change-Id: Icc9dfc05b18f9da96b29b7cadeb57f7218832295

diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 2a58e1273..de99f9ce1 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -76,6 +76,8 @@ bool ClientSession::_handleInput(const char *buffer, int length)
     const std::string firstLine = getFirstLine(buffer, length);
     const std::vector<std::string> tokens = LOOLProtocol::tokenize(firstLine.data(), firstLine.size());
 
+    checkTileRequestTimout();
+
     auto docBroker = getDocumentBroker();
     if (!docBroker)
     {
@@ -327,7 +329,13 @@ bool ClientSession::_handleInput(const char *buffer, int length)
     else if (tokens[0] == "tileprocessed")
     {
         if(_tilesOnFly > 0) // canceltiles message can zero this value
+        {
             --_tilesOnFly;
+            if(_tilesOnFly == 0)
+            {
+                _tileCounterStartTime = boost::none;
+            }
+        }
         docBroker->sendRequestedTiles(shared_from_this());
         return true;
     }
@@ -994,6 +1002,19 @@ Authorization ClientSession::getAuthorization() const
     return Authorization();
 }
 
+void ClientSession::setTilesOnFly(int tilesOnFly)
+{
+    _tilesOnFly = tilesOnFly;
+    if(tilesOnFly == 0)
+    {
+        _tileCounterStartTime = boost::none;
+    }
+    else
+    {
+        _tileCounterStartTime = std::chrono::steady_clock::now();
+    }
+}
+
 void ClientSession::onDisconnect()
 {
     LOG_INF(getName() << " Disconnected, current number of connections: " << LOOLWSD::NumConnections);
@@ -1130,4 +1151,17 @@ void ClientSession::handleTileInvalidation(const std::string& message,
     }
 }
 
+void ClientSession::checkTileRequestTimout()
+{
+    if(_tileCounterStartTime != boost::none)
+    {
+        const auto duration = std::chrono::steady_clock::now() - _tileCounterStartTime.get();
+        if( std::chrono::duration_cast<std::chrono::seconds>(duration).count() > 5)
+        {
+            LOG_WRN("Tile request timeout: server waits too long for tileprocessed messages.");
+            _tileCounterStartTime = boost::none;
+        }
+    }
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index 418a20cf8..2609d5f9a 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -110,7 +110,7 @@ public:
     boost::optional<TileCombined>& getRequestedTiles() { return _requestedTiles; }
 
     int getTilesOnFly() const { return _tilesOnFly; }
-    void setTilesOnFly(int tilesOnFly) { _tilesOnFly = tilesOnFly; }
+    void setTilesOnFly(int tilesOnFly);
 
 
 private:
@@ -153,6 +153,8 @@ private:
     void handleTileInvalidation(const std::string& message,
                                 const std::shared_ptr<DocumentBroker>& docBroker);
 
+    void checkTileRequestTimout();
+
 private:
     std::weak_ptr<DocumentBroker> _docBroker;
 
@@ -195,6 +197,7 @@ private:
     std::string _docType;
 
     int _tilesOnFly;
+    boost::optional<std::chrono::time_point<std::chrono::steady_clock>> _tileCounterStartTime;
 
     boost::optional<TileCombined> _requestedTiles;
 };
commit 12fe72dc75a7ad6cf8695ce7716de0fe99a92cf8
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Thu Jun 7 13:13:36 2018 +0200
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Aug 15 11:50:02 2018 +0200

    Wait tileprocessed message from client to send new bunch of tiles
    
    We always  one bunch of tiles (e.g. all tiles invalidated) and we
    are waiting until client send tileprocessed message back for all
    tiles before sending the new tiles.
    By canceltiles message we drop every previously requested tiles and
    make wsd ready to send new tiles, which will be requested by the client
    in theory.
    
    (cherry picked from commit 15afe2c0fb4b34de86f4473bb06349872e92333b)
    
    Change-Id: I9901420ada549e962ffaf5e6bd58e52b86bd129d

diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index b963546db..56fe4504b 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -1340,6 +1340,9 @@ L.TileLayer = L.GridLayer.extend({
 			tile.el.src = img;
 		}
 		L.Log.log(textMsg, L.INCOMING, key);
+
+		// Send acknowledgment, that the tile message arrived
+		this._map._socket.sendMessage('tileprocessed tile= ' + key);
 	},
 
 	_tileOnLoad: function (done, tile) {
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index d70775daa..2a58e1273 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -48,7 +48,8 @@ ClientSession::ClientSession(const std::string& id,
     _tileWidthPixel(0),
     _tileHeightPixel(0),
     _tileWidthTwips(0),
-    _tileHeightTwips(0)
+    _tileHeightTwips(0),
+    _tilesOnFly(0)
 {
     const size_t curConnections = ++LOOLWSD::NumConnections;
     LOG_INF("ClientSession ctor [" << getName() << "], current number of connections: " << curConnections);
@@ -131,6 +132,7 @@ bool ClientSession::_handleInput(const char *buffer, int length)
         return loadDocument(buffer, length, tokens, docBroker);
     }
     else if (tokens[0] != "canceltiles" &&
+             tokens[0] != "tileprocessed" &&
              tokens[0] != "clientzoom" &&
              tokens[0] != "clientvisiblearea" &&
              tokens[0] != "outlinestate" &&
@@ -322,6 +324,13 @@ bool ClientSession::_handleInput(const char *buffer, int length)
             return forwardToChild(std::string(buffer, length), docBroker);
         }
     }
+    else if (tokens[0] == "tileprocessed")
+    {
+        if(_tilesOnFly > 0) // canceltiles message can zero this value
+            --_tilesOnFly;
+        docBroker->sendRequestedTiles(shared_from_this());
+        return true;
+    }
     else
     {
         if (tokens[0] == "key")
@@ -1069,11 +1078,13 @@ void ClientSession::handleTileInvalidation(const std::string& message,
 {
     docBroker->invalidateTiles(message);
 
+    bool bIsTextDocument = _docType.find("text") != std::string::npos;
+
     // Skip requesting new tiles if we don't have client visible area data yet.
     if(!_clientVisibleArea.hasSurface() ||
        _tileWidthPixel == 0 || _tileHeightPixel == 0 ||
        _tileWidthTwips == 0 || _tileHeightTwips == 0 ||
-       _clientSelectedPart == -1)
+       (_clientSelectedPart == -1 && !bIsTextDocument))
     {
         return;
     }
@@ -1082,14 +1093,14 @@ void ClientSession::handleTileInvalidation(const std::string& message,
     int part = result.first;
     Util::Rectangle& invalidateRect = result.second;
 
-    if(_docType.find("text") != std::string::npos) // For Writer we don't have separate parts
+    if(bIsTextDocument) // For Writer we don't have separate parts
         part = 0;
 
     if(part == -1) // If no part is specifed we use the part used by the client
         part = _clientSelectedPart;
 
     std::vector<TileDesc> invalidTiles;
-    if(part == _clientSelectedPart)
+    if(part == _clientSelectedPart || bIsTextDocument)
     {
         Util::Rectangle intersection;
         intersection._x1 = std::max(invalidateRect._x1, _clientVisibleArea._x1);
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index fe088511d..418a20cf8 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -17,9 +17,11 @@
 #include "DocumentBroker.hpp"
 #include <Poco/URI.h>

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list