common/Rectangle.hpp | 5 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 | 69 +------- loleaflet/src/layer/tile/WriterTileLayer.js | 41 ---- test/TileCacheTests.cpp | 4 wsd/ClientSession.cpp | 227 ++++++++++++++++++++++++++- wsd/ClientSession.hpp | 55 ++++++ wsd/DocumentBroker.cpp | 167 ++++++++++++++++--- wsd/DocumentBroker.hpp | 1 wsd/TestStubs.cpp | 5 wsd/TileCache.cpp | 27 ++- wsd/TileCache.hpp | 7 wsd/protocol.txt | 5 15 files changed, 551 insertions(+), 254 deletions(-)
New commits: commit 33a0cb1ee7f1b3e6a3e234a257a6db1848ba01bc Author: Tamás Zolnai <[email protected]> Date: Thu Jul 19 14:19:07 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 diff --git a/test/TileCacheTests.cpp b/test/TileCacheTests.cpp index 10d8ba648..09fe0671e 100644 --- a/test/TileCacheTests.cpp +++ b/test/TileCacheTests.cpp @@ -247,6 +247,8 @@ void TileCacheTests::testPerformance() std::vector<char> 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. @@ -430,6 +432,8 @@ void TileCacheTests::testUnresponsiveClient() std::vector<char> 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 fe5507f134c23198eb78276836b619fa227600fa Author: Tamás Zolnai <[email protected]> Date: Tue Jul 10 14:23:20 2018 +0200 Add some additional comment related to latency changes Change-Id: I3ece60ce8a66730a8f8a93757412bcaa2b02a77d diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index a62f4b39d..1b21425d4 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -111,8 +111,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(); } @@ -161,12 +163,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: @@ -218,11 +222,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 3215ddfe481e00cf75191841ee052a38fc30104c Author: Tamás Zolnai <[email protected]> Date: Fri Jul 6 13:48:30 2018 +0200 Set back debugging code removed by latency related code changes Change-Id: I6634029bc8c36dfea7219e7e48e1c010b274e687 diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js index 10cdb155b..b616155b0 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 da532cf97..b7c451437 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 83c78ccfb..4549a6c31 100644 --- a/loleaflet/src/layer/tile/ImpressTileLayer.js +++ b/loleaflet/src/layer/tile/ImpressTileLayer.js @@ -365,7 +365,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); @@ -379,6 +379,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]); } @@ -391,6 +392,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 8d92b0809de30faeef3819022be6628f634f85f2 Author: Tamás Zolnai <[email protected]> Date: Fri Jul 6 12:38:31 2018 +0200 Store wiredIDs on the server side So we can use this information in tile requests. Change-Id: I87ba420ec0fd699353d48a228268e546ace21921 diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 8cc1f5d94..9d8cc9e8e 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -1403,17 +1403,11 @@ L.TileLayer = L.GridLayer.extend({ } ctx.putImageData(imgData, 0, 0); - - tile.oldWireId = tile.wireId; - tile.wireId = command.wireId; tile.el.src = canvas.toDataURL('image/png'); console.log('set new image'); } 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 205775b36..41fa32193 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -293,6 +293,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); } } @@ -310,6 +311,7 @@ bool ClientSession::_handleInput(const char *buffer, int length) else { _clientSelectedPart = temp; + resetWireIdMap(); return forwardToChild(std::string(buffer, length), docBroker); } } @@ -332,6 +334,7 @@ bool ClientSession::_handleInput(const char *buffer, int length) _tileHeightPixel = tilePixelHeight; _tileWidthTwips = tileTwipWidth; _tileHeightTwips = tileTwipHeight; + resetWireIdMap(); return forwardToChild(std::string(buffer, length), docBroker); } } @@ -941,6 +944,7 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt if(getTokenInteger(token, "current", part)) { _clientSelectedPart = part; + resetWireIdMap(); } // Get document type too @@ -1074,10 +1078,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() @@ -1202,7 +1203,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); } } @@ -1216,4 +1223,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 068631044..a62f4b39d 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; @@ -120,7 +121,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. @@ -161,6 +164,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; @@ -207,12 +215,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 5d019a784..7eead269b 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 963bf9618..0a1f7524b 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 b2d8ca793..84dd5b3bd 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]; std::shared_ptr<ClientSession> 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 std::shared_ptr<ClientSession> session = subscriber.lock(); if (session) { + session->traceTileBySend(tile); session->enqueueSendMessage(payload); } } commit e1b22eaac325a8ee2fe822f5cb37969b80f1fd13 Author: Tamás Zolnai <[email protected]> Date: Thu Jul 5 14:40:28 2018 +0200 Calculate TilesOnFly limit based on visible area Use 10 as a minimum value. Change-Id: I9442a427fd25e1a7a32c3d1d06aa34d2c4ca2472 diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index efa7df4f5..068631044 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -116,6 +116,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 d17aeb7cb..5d019a784 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 2fda5f7d925c26c8ec0de672842c40be73ee3bc8 Author: Tamás Zolnai <[email protected]> Date: Tue Jul 10 14:05:36 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). Change-Id: I22ff3e35c8ded6001c7fc160abdc1f1b12ce3bae diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index bb1213011..205775b36 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -84,8 +84,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(); - std::shared_ptr<DocumentBroker> docBroker = getDocumentBroker(); if (!docBroker) { @@ -350,10 +348,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; } @@ -1078,25 +1072,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() @@ -1230,17 +1216,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 3e5ae2d5f..efa7df4f5 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; @@ -110,10 +110,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: @@ -156,8 +157,6 @@ private: void handleTileInvalidation(const std::string& message, const std::shared_ptr<DocumentBroker>& docBroker); - void checkTileRequestTimout(); - private: std::weak_ptr<DocumentBroker> _docBroker; @@ -207,10 +206,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 c575eba82..d17aeb7cb 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 std::string canceltiles = tileCache().cancelTiles(session); commit 4e2b50dc0fb3af8016dd14ca90fa563e8f0146c4 Author: Tamás Zolnai <[email protected]> Date: Tue Jun 19 16:41:02 2018 +0200 Check whether tile rendering request was already sent Change-Id: Iceb559106dcd95d6ff7db67df76cdfb04f9fb7e0 diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index bbcb2b7b9..c575eba82 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 ef697933a..b2d8ca793 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 6c4e4440e89a9a4d8e2747a280c8e96c9d142069 Author: Tamás Zolnai <[email protected]> Date: Tue Jun 19 16:15:37 2018 +0200 Handle part number a bit more robust in case of Writer Change-Id: I7390f1c5f4289be67deacf3540068c040b230584 diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 89a1b8155..bb1213011 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -55,7 +55,8 @@ ClientSession::ClientSession(const std::string& id, _tileWidthPixel(0), _tileHeightPixel(0), _tileWidthTwips(0), - _tileHeightTwips(0) + _tileHeightTwips(0), + _isTextDocument(false) { assert(!creatingPngThumbnail || thumbnailFile != ""); const size_t curConnections = ++LOOLWSD::NumConnections; @@ -299,17 +300,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)) - { - sendTextFrame("error: cmd=setclientpart kind=syntax"); - return false; - } - else + if(!_isTextDocument) { - _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") @@ -736,17 +740,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:") { @@ -946,7 +953,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; } } @@ -1180,7 +1187,7 @@ void ClientSession::handleTileInvalidation(const std::string& message, if(!_clientVisibleArea.hasSurface() || _tileWidthPixel == 0 || _tileHeightPixel == 0 || _tileWidthTwips == 0 || _tileHeightTwips == 0 || - _clientSelectedPart == -1) + (_clientSelectedPart == -1 && !_isTextDocument)) { return; } @@ -1189,14 +1196,11 @@ 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 - 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) + 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 e941fa842..3e5ae2d5f 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -205,7 +205,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 7428c46efe1dd2f948ca9754ab5dc719ff991f4b Author: Tamás Zolnai <[email protected]> Date: Tue Jul 10 14:12:11 2018 +0200 In Impress setpart message's syntax is a bit different. Two alternative sytnax: "setpart part=1" "setpart 1" Change-Id: I42683ca46d642d56cfc3dcc52a10d69a3f00462b diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 5ddf439c9..89a1b8155 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -738,7 +738,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 1aa8f3b17bc7d6489d0c8dc45cec4acac4938871 Author: Tamás Zolnai <[email protected]> Date: Fri Jun 15 16:33:28 2018 +0200 Send new clientvisiblearea and clientzoom messages when something is actually changed. Change-Id: I56983f5700cb9cbd0b660155a4dd0a2396b22e2a diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js index 3e0bf2dfa..10cdb155b 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 f4f2f05f1..da532cf97 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) { 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 c31b89ceb..8cc1f5d94 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -149,15 +149,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) { @@ -259,9 +255,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); @@ -492,7 +486,6 @@ L.TileLayer = L.GridLayer.extend({ }, toggleTileDebugMode: function() { - this._invalidateClientVisibleArea(); this._debug = !this._debug; if (!this._debug) { this._map.removeLayer(this._debugInfo); @@ -2270,13 +2263,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 29df46219c1fcabc2dad74699127a8bf57acb8a2 Author: Tamás Zolnai <[email protected]> Date: Sat Jun 16 14:22:01 2018 +0200 Need to extract the initial part id from status message Change-Id: Ia0651d93fedb71d3ca1e24d0356ac179e95e907e diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 5197ec39f..5ddf439c9 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -927,11 +927,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 85f96bc281f037087fb226b018c40b8410d353d5 Author: Tamás Zolnai <[email protected]> Date: Wed Jun 13 15:04:09 2018 +0200 Store sent tiles's id instead of using a simple counter Change-Id: I8cbf84923a53fb6b294bd4039eb7382326f8c445 diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index d8c0de348..c31b89ceb 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -1435,7 +1435,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 5ed9e931f..5197ec39f 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -12,6 +12,7 @@ #include "ClientSession.hpp" #include <fstream> +#include <sstream> #include <Poco/File.h> #include <Poco/Net/HTTPResponse.h> @@ -54,8 +55,7 @@ ClientSession::ClientSession(const std::string& id, _tileWidthPixel(0), _tileHeightPixel(0), _tileWidthTwips(0), - _tileHeightTwips(0), - _tilesOnFly(0) + _tileHeightTwips(0) { assert(!creatingPngThumbnail || thumbnailFile != ""); const size_t curConnections = ++LOOLWSD::NumConnections; @@ -335,13 +335,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; @@ -1048,15 +1055,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 366cb653a..e941fa842 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; @@ -111,8 +112,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: @@ -206,7 +207,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 71f7bea37..bbcb2b7b9 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 std::string canceltiles = tileCache().cancelTiles(session); diff --git a/wsd/protocol.txt b/wsd/protocol.txt index c7a126670..7c8d7e6a2 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 464dd72e1c3471a7e22b5d9cb4c7437e407fa4ba Author: Tamás Zolnai <[email protected]> Date: Tue Jun 12 14:51:38 2018 +0200 We might need to rerequest tile rendering when we are at sending them Change-Id: I0551e51c5f5023931dad13435b4ac3517fc48931 diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 1478af959..71f7bea37 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()) { TileCombined 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 30f4cafd3758c3003cbcd44f5923336aa6be0af4 Author: Tamás Zolnai <[email protected]> Date: Mon Jun 11 16:26:09 2018 +0200 Reduce code deduplication We can request tilecombine even if client needs actually one tile only. Change-Id: Id897f219885be4cb93635d727d4ee871a4b55cb7 diff --git a/loleaflet/src/layer/tile/GridLayer.js b/loleaflet/src/layer/tile/GridLayer.js index 2489a704c..f4f2f05f1 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 1f2982cdc53236b0312438a1bd812d8d203ac4fb Author: Tamás Zolnai <[email protected]> Date: Sat Jun 9 21:33:44 2018 +0200 Send the right visible area to the server Change-Id: I036dfaa566fa7d4e370386d839bd2397cbf929f8 diff --git a/loleaflet/src/layer/tile/GridLayer.js b/loleaflet/src/layer/tile/GridLayer.js index 11fbebed5..2489a704c 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 96a1299ff..d8c0de348 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -149,10 +149,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; @@ -1479,22 +1476,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 + @@ -1521,21 +1506,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); }, @@ -2301,17 +2276,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 a9c5ea9022e78969118f23d5784a04df3f3b1e36 Author: Tamás Zolnai <[email protected]> Date: Tue Jul 10 14:10:28 2018 +0200 Add a timeout for tileprocessed message handling For debug purposes. Change-Id: Icc9dfc05b18f9da96b29b7cadeb57f7218832295 diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index d975c7200..5ed9e931f 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -83,6 +83,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(); + std::shared_ptr<DocumentBroker> docBroker = getDocumentBroker(); if (!docBroker) { @@ -334,7 +336,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; } @@ -1040,6 +1048,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); @@ -1174,4 +1195,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 53e5201b5..366cb653a 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -112,7 +112,7 @@ public: boost::optional<TileCombined>& getRequestedTiles() { return _requestedTiles; } int getTilesOnFly() const { return _tilesOnFly; } - void setTilesOnFly(int tilesOnFly) { _tilesOnFly = tilesOnFly; } + void setTilesOnFly(int tilesOnFly); private: @@ -155,6 +155,8 @@ private: void handleTileInvalidation(const std::string& message, const std::shared_ptr<DocumentBroker>& docBroker); + void checkTileRequestTimout(); + private: std::weak_ptr<DocumentBroker> _docBroker; @@ -205,6 +207,7 @@ private: std::string _docType; int _tilesOnFly; + boost::optional<std::chrono::time_point<std::chrono::steady_clock>> _tileCounterStartTime; boost::optional<TileCombined> _requestedTiles; }; commit 15afe2c0fb4b34de86f4473bb06349872e92333b Author: Tamás Zolnai <[email protected]> Date: Thu Jun 7 13:13:36 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. Change-Id: I9901420ada549e962ffaf5e6bd58e52b86bd129d diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index e464b41a8..96a1299ff 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -1436,6 +1436,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 582a346e8..d975c7200 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -54,7 +54,8 @@ ClientSession::ClientSession(const std::string& id, _tileWidthPixel(0), _tileHeightPixel(0), _tileWidthTwips(0), - _tileHeightTwips(0) + _tileHeightTwips(0), + _tilesOnFly(0) { assert(!creatingPngThumbnail || thumbnailFile != ""); const size_t curConnections = ++LOOLWSD::NumConnections; @@ -138,6 +139,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" && @@ -329,6 +331,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") diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index c0eaf9535..53e5201b5 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -17,9 +17,11 @@ #include "DocumentBroker.hpp" #include <Poco/URI.h> #include <Rectangle.hpp> +#include <boost/optional.hpp> class DocumentBroker; + /// Represents a session to a LOOL client, in the WSD process. class ClientSession final : public Session, public std::enable_shared_from_this<ClientSession> { @@ -107,6 +109,12 @@ public: /// Set WOPI fileinfo object void setWopiFileInfo(std::unique_ptr<WopiStorage::WOPIFileInfo>& wopiFileInfo) { _wopiFileInfo = std::move(wopiFileInfo); } + boost::optional<TileCombined>& getRequestedTiles() { return _requestedTiles; } + + int getTilesOnFly() const { return _tilesOnFly; } + void setTilesOnFly(int tilesOnFly) { _tilesOnFly = tilesOnFly; } + + private: /// SocketHandler: disconnection event. @@ -195,8 +203,13 @@ private: // Type of the docuemnt, extracter from status message std::string _docType; + + int _tilesOnFly; + + boost::optional<TileCombined> _requestedTiles; }; + #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 4d465a39d..1478af959 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -1290,61 +1290,136 @@ void DocumentBroker::handleTileCombinedRequest(TileCombined& tileCombined, LOG_TRC("TileCombined request for " << tileCombined.serialize()); - // Satisfy as many tiles from the cache. - std::vector<TileDesc> tiles; + // Check which newly requested tiles needs rendering. + std::vector<TileDesc> tilesNeedsRendering; for (auto& tile : tileCombined.getTiles()) { std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(tile); - if (cachedTile) - { - //TODO: Combine the response to reduce latency. -#if ENABLE_DEBUG - const std::string response = tile.serialize("tile:") + " renderid=cached\n"; -#else - const std::string response = tile.serialize("tile:") + "\n"; -#endif - - std::vector<char> output; - output.reserve(static_cast<size_t>(4) * tile.getWidth() * tile.getHeight()); - output.resize(response.size()); - std::memcpy(output.data(), response.data(), response.size()); - - assert(cachedTile->is_open()); - cachedTile->seekg(0, std::ios_base::end); - const size_t pos = output.size(); - std::streamsize size = cachedTile->tellg(); - output.resize(pos + size); - cachedTile->seekg(0, std::ios_base::beg); - cachedTile->read(output.data() + pos, size); + if(cachedTile) cachedTile->close(); - - session->sendBinaryFrame(output.data(), output.size()); - } else { // Not cached, needs rendering. tile.setVersion(++_tileVersion); - tileCache().subscribeToTileRendering(tile, session); - tiles.push_back(tile); + tilesNeedsRendering.push_back(tile); _debugRenderedTileCount++; } } - if (!tiles.empty()) + // Send rendering request + if (!tilesNeedsRendering.empty()) { - TileCombined newTileCombined = TileCombined::create(tiles); + TileCombined newTileCombined = TileCombined::create(tilesNeedsRendering); // Forward to child to render. const std::string req = newTileCombined.serialize("tilecombine"); LOG_DBG("Sending residual tilecombine: " << req); _childProcess->sendTextFrame(req); } + + // Accumulate tiles + boost::optional<TileCombined>& requestedTiles = session->getRequestedTiles(); + if(requestedTiles == boost::none) + { + requestedTiles = TileCombined::create(tileCombined.getTiles()); + } + // Drop duplicated tiles, but use newer version number + else + { + for (const auto& newTile : tileCombined.getTiles()) + { + const TileDesc& firstOldTile = requestedTiles.get().getTiles()[0]; + if(newTile.getPart() != firstOldTile.getPart() || + newTile.getWidth() != firstOldTile.getWidth() || + newTile.getHeight() != firstOldTile.getHeight() || + newTile.getTileWidth() != firstOldTile.getTileWidth() || + newTile.getTileHeight() != firstOldTile.getTileHeight() ) + { + LOG_WRN("Different visible area information in tile requests"); + } + + bool tileFound = false; + for (auto& oldTile : requestedTiles.get().getTiles()) + { + if(oldTile.getTilePosX() == newTile.getTilePosX() && + oldTile.getTilePosY() == newTile.getTilePosY() ) + { + oldTile.setVersion(newTile.getVersion()); + oldTile.setOldWireId(newTile.getOldWireId()); + oldTile.setWireId(newTile.getWireId()); + tileFound = true; + break; + } + } + if(!tileFound) + requestedTiles.get().getTiles().push_back(newTile); + } + } + + lock.unlock(); + lock.release(); + sendRequestedTiles(session); +} + +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()) + { + session->setTilesOnFly(requestedTiles.get().getTiles().size()); + + // Satisfy as many tiles from the cache. + for (auto& tile : requestedTiles.get().getTiles()) + { + std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(tile); + if (cachedTile) + { + //TODO: Combine the response to reduce latency. +#if ENABLE_DEBUG + const std::string response = tile.serialize("tile:") + " renderid=cached\n"; +#else + const std::string response = tile.serialize("tile:") + "\n"; +#endif + + std::vector<char> output; + output.reserve(static_cast<size_t>(4) * tile.getWidth() * tile.getHeight()); + output.resize(response.size()); + std::memcpy(output.data(), response.data(), response.size()); + + assert(cachedTile->is_open()); + cachedTile->seekg(0, std::ios_base::end); + const auto pos = output.size(); + std::streamsize size = cachedTile->tellg(); + output.resize(pos + size); + cachedTile->seekg(0, std::ios_base::beg); + cachedTile->read(output.data() + pos, size); + cachedTile->close(); + + session->sendBinaryFrame(output.data(), output.size()); + } + else + { + // Not cached, needs rendering. Rendering request was already sent + tileCache().subscribeToTileRendering(tile, session); + } + } + requestedTiles = boost::none; + } } void DocumentBroker::cancelTileRequests(const std::shared_ptr<ClientSession>& session) { std::unique_lock<std::mutex> lock(_mutex); + // Clear tile requests + session->setTilesOnFly(0); + session->getRequestedTiles() = boost::none; + const std::string canceltiles = tileCache().cancelTiles(session); if (!canceltiles.empty()) { diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp index 92c39b2e5..863a3d664 100644 --- a/wsd/DocumentBroker.hpp +++ b/wsd/DocumentBroker.hpp @@ -301,6 +301,7 @@ public: void handleDialogRequest(const std::string& dialogCmd); void handleTileCombinedRequest(TileCombined& tileCombined, const std::shared_ptr<ClientSession>& session); + void sendRequestedTiles(const std::shared_ptr<ClientSession>& session); void cancelTileRequests(const std::shared_ptr<ClientSession>& session); void handleTileResponse(const std::vector<char>& payload); void handleDialogPaintResponse(const std::vector<char>& payload, bool child); diff --git a/wsd/protocol.txt b/wsd/protocol.txt index 1697a8de4..c7a126670 100644 --- a/wsd/protocol.txt +++ b/wsd/protocol.txt @@ -31,6 +31,11 @@ canceltiles parameter. There is no guarantee of exactly which tile: messages might still be sent back to the client. +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> + downloadas name=<fileName> id=<id> format=<document format> options=<SkipImages, etc> Exports the current document to the desired format and returns a download URL commit 57cdd68fcf84feb4a150c03c225c78cdb677479b Author: Tamás Zolnai <[email protected]> Date: Thu May 31 20:20:09 2018 +0200 Request new tiles in wsd by invalidateTiles message And don't wait for the client to send back a tilecombine request. Change-Id: I9ea5de0f6b12dfaaf61992d34735d5b78ea382ed diff --git a/common/Rectangle.hpp b/common/Rectangle.hpp index c6c6efce4..1300aa71a 100644 --- a/common/Rectangle.hpp +++ b/common/Rectangle.hpp @@ -73,6 +73,11 @@ struct Rectangle { return _x1 <= _x2 && _y1 <= _y2; } + + bool hasSurface() + { + return _x1 < _x2 && _y1 < _y2; + } }; } diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js index a53a6e34a..3e0bf2dfa 100644 --- a/loleaflet/src/layer/tile/CalcTileLayer.js +++ b/loleaflet/src/layer/tile/CalcTileLayer.js @@ -242,9 +242,6 @@ L.CalcTileLayer = L.TileLayer.extend({ command.height = parseInt(strTwips[3]); command.part = this._selectedPart; } - if (this._docType === 'text') { - command.part = 0; - } var topLeftTwips = new L.Point(command.x, command.y); var offset = new L.Point(command.width, command.height); var bottomRightTwips = topLeftTwips.add(offset); @@ -256,11 +253,6 @@ L.CalcTileLayer = L.TileLayer.extend({ var visibleBottomRight = this._latLngToTwips(this._map.getBounds().getSouthEast()); var visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight); - var tilePositionsX = ''; - var tilePositionsY = ''; - var oldWireIds = ''; - var needsNewTiles = false; - for (var key in this._tiles) { var coords = this._tiles[key].coords; var tileTopLeft = this._coordsToTwips(coords); @@ -274,24 +266,6 @@ L.CalcTileLayer = L.TileLayer.extend({ this._tiles[key]._invalidCount = 1; } if (visibleArea.intersects(bounds)) { - if (tilePositionsX !== '') { - tilePositionsX += ','; - } - tilePositionsX += tileTopLeft.x; - if (tilePositionsY !== '') { - tilePositionsY += ','; - } - tilePositionsY += tileTopLeft.y; - if (oldWireIds !== '') { - oldWireIds += ','; - } - if (this._tiles[key].oldWireId === undefined) { - oldWireIds += '0'; - } - else { - oldWireIds += this._tiles[key].oldWireId; - } - needsNewTiles = true; if (this._debug) { this._debugAddInvalidationData(this._tiles[key]); } @@ -304,24 +278,6 @@ L.CalcTileLayer = L.TileLayer.extend({ } } - if (needsNewTiles && command.part === this._selectedPart) - { - var message = 'tilecombine ' + - 'part=' + command.part + ' ' + - 'width=' + this._tileWidthPx + ' ' + - 'height=' + this._tileHeightPx + ' ' + - 'tileposx=' + tilePositionsX + ' ' + - 'tileposy=' + tilePositionsY + ' ' + - 'tilewidth=' + this._tileWidthTwips + ' ' + - 'tileheight=' + this._tileHeightTwips + ' ' + - 'oldwid=' + oldWireIds; - - this._map._socket.sendMessage(message, ''); - if (this._debug) { - this._debugAddInvalidationMessage(message); - } - } - 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/ImpressTileLayer.js b/loleaflet/src/layer/tile/ImpressTileLayer.js index abad6a799..83c78ccfb 100644 --- a/loleaflet/src/layer/tile/ImpressTileLayer.js +++ b/loleaflet/src/layer/tile/ImpressTileLayer.js @@ -366,11 +366,6 @@ L.ImpressTileLayer = L.TileLayer.extend({ var visibleBottomRight = this._latLngToTwips(this._map.getBounds().getSouthEast()); var visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight); - var tilePositionsX = ''; - var tilePositionsY = ''; - var oldWireIds = ''; - var needsNewTiles = false; - for (var key in this._tiles) { var coords = this._tiles[key].coords; var tileTopLeft = this._coordsToTwips(coords); @@ -384,24 +379,6 @@ L.ImpressTileLayer = L.TileLayer.extend({ this._tiles[key]._invalidCount = 1; } if (visibleArea.intersects(bounds)) { - if (tilePositionsX !== '') { - tilePositionsX += ','; - } - tilePositionsX += tileTopLeft.x; - if (tilePositionsY !== '') { - tilePositionsY += ','; - } - tilePositionsY += tileTopLeft.y; - if (oldWireIds !== '') { - oldWireIds += ','; - } - if (this._tiles[key].oldWireId === undefined) { - oldWireIds += '0'; - } - else { - oldWireIds += this._tiles[key].oldWireId; - } - needsNewTiles = true; if (this._debug) { this._debugAddInvalidationData(this._tiles[key]); } @@ -414,24 +391,6 @@ L.ImpressTileLayer = L.TileLayer.extend({ } } - if (needsNewTiles && command.part === this._selectedPart) - { - var message = 'tilecombine ' + - 'part=' + command.part + ' ' + - 'width=' + this._tileWidthPx + ' ' + - 'height=' + this._tileHeightPx + ' ' + - 'tileposx=' + tilePositionsX + ' ' + - 'tileposy=' + tilePositionsY + ' ' + - 'tilewidth=' + this._tileWidthTwips + ' ' + - 'tileheight=' + this._tileHeightTwips + ' ' + - 'oldwid=' + oldWireIds; - - this._map._socket.sendMessage(message, ''); - if (this._debug) { - this._debugAddInvalidationMessage(message); - } - } - 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 6c1a886b8..8c9f46e81 100644 --- a/loleaflet/src/layer/tile/WriterTileLayer.js +++ b/loleaflet/src/layer/tile/WriterTileLayer.js @@ -109,10 +109,6 @@ 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 tilePositionsX = ''; - var tilePositionsY = ''; - var oldWireIds = ''; - var needsNewTiles = false; for (var key in this._tiles) { var coords = this._tiles[key].coords; var tileTopLeft = this._coordsToTwips(coords); @@ -126,24 +122,6 @@ L.WriterTileLayer = L.TileLayer.extend({ this._tiles[key]._invalidCount = 1; } if (visibleArea.intersects(bounds)) { - if (tilePositionsX !== '') { - tilePositionsX += ','; - } - tilePositionsX += tileTopLeft.x; - if (tilePositionsY !== '') { - tilePositionsY += ','; - } - tilePositionsY += tileTopLeft.y; - if (oldWireIds !== '') { - oldWireIds += ','; - } - if (this._tiles[key].oldWireId === undefined) { - oldWireIds += '0'; - } - else { - oldWireIds += this._tiles[key].oldWireId; - } - needsNewTiles = true; if (this._debug) { this._debugAddInvalidationData(this._tiles[key]); } @@ -156,28 +134,6 @@ L.WriterTileLayer = L.TileLayer.extend({ } } - if (needsNewTiles) - { - // CalcTileLayer.js and ImpressTileLayer.js avoid this when - // command.part !== this._selectedPart; but in Writer, the part is - // always 0 anyway - var message = 'tilecombine ' + - 'part=' + command.part + ' ' + - 'width=' + this._tileWidthPx + ' ' + - 'height=' + this._tileHeightPx + ' ' + - 'tileposx=' + tilePositionsX + ' ' + - 'tileposy=' + tilePositionsY + ' ' + - 'tilewidth=' + this._tileWidthTwips + ' ' + - 'tileheight=' + this._tileHeightTwips + ' ' + - 'oldwid=' + oldWireIds; - - this._map._socket.sendMessage(message, ''); - - if (this._debug) { - this._debugAddInvalidationMessage(message); - } - } - for (key in this._tileCache) { // compute the rectangle that each tile covers in the document based // on the zoom level diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 67fe8bd20..582a346e8 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -50,7 +50,7 @@ ClientSession::ClientSession(const std::string& id, _isViewLoaded(false), _keyEvents(1), _clientVisibleArea(0, 0, 0, 0), - _clientSelectedPart(0), + _clientSelectedPart(-1), _tileWidthPixel(0), _tileHeightPixel(0), _tileWidthTwips(0), @@ -903,6 +903,13 @@ 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) + { + _docType = stringMsg.substr(index, stringMsg.find_first_of(' ', index) - index); + } + // Forward the status response to the client. return forwardToClient(payload); } @@ -928,7 +935,7 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt else if (tokens[0] == "invalidatetiles:") { assert(firstLine.size() == static_cast<std::string::size_type>(length)); - docBroker->invalidateTiles(firstLine); + handleTileInvalidation(firstLine, docBroker); } else if (tokens[0] == "invalidatecursor:") { @@ -1103,4 +1110,59 @@ void ClientSession::dumpState(std::ostream& os) } +void ClientSession::handleTileInvalidation(const std::string& message, + const std::shared_ptr<DocumentBroker>& docBroker) +{ + docBroker->invalidateTiles(message); + + // 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) + { + return; + } + + std::pair<int, Util::Rectangle> result = TileCache::parseInvalidateMsg(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 + 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) + { + Util::Rectangle intersection; + intersection._x1 = std::max(invalidateRect._x1, _clientVisibleArea._x1); + intersection._y1 = std::max(invalidateRect._y1, _clientVisibleArea._y1); + intersection._x2 = std::min(invalidateRect._x2, _clientVisibleArea._x2); + 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 j = std::ceil(intersection._y1 / _tileHeightTwips); + 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); + invalidTiles.back().setWireId(0); + } + } + } + } + + if(!invalidTiles.empty()) + { + TileCombined tileCombined = TileCombined::create(invalidTiles); + docBroker->handleTileCombinedRequest(tileCombined, shared_from_this()); + } +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index 919bc8dbd..c0eaf9535 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -144,6 +144,9 @@ private: void dumpState(std::ostream& os) override; + void handleTileInvalidation(const std::string& message, + const std::shared_ptr<DocumentBroker>& docBroker); + private: std::weak_ptr<DocumentBroker> _docBroker; @@ -189,6 +192,9 @@ private: int _tileHeightPixel; int _tileWidthTwips; int _tileHeightTwips; + + // Type of the docuemnt, extracter from status message + std::string _docType; }; #endif diff --git a/wsd/TileCache.cpp b/wsd/TileCache.cpp index 51f1933de..ef697933a 100644 --- a/wsd/TileCache.cpp +++ b/wsd/TileCache.cpp @@ -339,22 +339,27 @@ void TileCache::invalidateTiles(int part, int x, int y, int width, int height) void TileCache::invalidateTiles(const std::string& tiles) { + std::pair<int, Util::Rectangle> result = TileCache::parseInvalidateMsg(tiles); + Util::Rectangle& invalidateRect = result.second; + invalidateTiles(result.first, invalidateRect.getLeft(), invalidateRect.getTop(), invalidateRect.getWidth(), invalidateRect.getHeight()); +} + +std::pair<int, Util::Rectangle> TileCache::parseInvalidateMsg(const std::string& tiles) +{ StringTokenizer tokens(tiles, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); assert(tokens[0] == "invalidatetiles:"); if (tokens.count() == 2 && tokens[1] == "EMPTY") { - invalidateTiles(-1, 0, 0, INT_MAX, INT_MAX); - return; + return std::pair<int, Util::Rectangle>(-1, Util::Rectangle(0, 0, INT_MAX, INT_MAX)); } else if (tokens.count() == 3 && tokens[1] == "EMPTY,") { int part = 0; if (stringToInteger(tokens[2], part)) { - invalidateTiles(part, 0, 0, INT_MAX, INT_MAX); - return; + return std::pair<int, Util::Rectangle>(part, Util::Rectangle(0, 0, INT_MAX, INT_MAX)); } } else @@ -367,12 +372,13 @@ void TileCache::invalidateTiles(const std::string& tiles) getTokenInteger(tokens[4], "width", width) && getTokenInteger(tokens[5], "height", height)) { - invalidateTiles(part, x, y, width, height); - return; + + return std::pair<int, Util::Rectangle>(part, Util::Rectangle(x, y, width, height)); } } LOG_ERR("Unexpected invalidatetiles request [" << tiles << "]."); + return std::pair<int, Util::Rectangle>(-1, Util::Rectangle(0, 0, 0, 0)); } void TileCache::removeFile(const std::string& fileName) diff --git a/wsd/TileCache.hpp b/wsd/TileCache.hpp index 5f8e71cfe..d8c48eaa4 100644 --- a/wsd/TileCache.hpp +++ b/wsd/TileCache.hpp @@ -17,6 +17,7 @@ #include <string> #include <Poco/Timestamp.h> +#include <Rectangle.hpp> #include "TileDesc.hpp" @@ -72,6 +73,9 @@ public: // The tiles parameter is an invalidatetiles: message as sent by the child process void invalidateTiles(const std::string& tiles); + /// Parse invalidateTiles message to a part number and a rectangle of the invalidated area + static std::pair<int, Util::Rectangle> parseInvalidateMsg(const std::string& tiles); + /// Store the timestamp to modtime.txt. void saveLastModified(const Poco::Timestamp& timestamp); commit 161695eb66f7da5d7a9051b186306f39395fb8c0 Author: Tamás Zolnai <[email protected]> Date: Wed May 30 19:35:13 2018 +0200 Store client's visible are information in wsd Change-Id: Iec3c146181b7db2e76247d5775076e6ac90eed2c diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 1a7cb886f..e464b41a8 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -1481,10 +1481,23 @@ L.TileLayer = L.GridLayer.extend({ 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._map._socket.sendMessage('mouse type=' + type + ' x=' + x + ' y=' + y + ' count=' + count + ' buttons=' + buttons + ' modifier=' + modifier); + if (type === 'buttondown') { this._clearSearchResults(); } diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 0cd76c9f8..67fe8bd20 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -48,7 +48,13 @@ ClientSession::ClientSession(const std::string& id, _isDocumentOwner(false), _isAttached(false), _isViewLoaded(false), - _keyEvents(1) + _keyEvents(1), + _clientVisibleArea(0, 0, 0, 0), + _clientSelectedPart(0), + _tileWidthPixel(0), + _tileHeightPixel(0), + _tileWidthTwips(0), + _tileHeightTwips(0) { assert(!creatingPngThumbnail || thumbnailFile != ""); const size_t curConnections = ++LOOLWSD::NumConnections; @@ -266,6 +272,63 @@ bool ClientSession::_handleInput(const char *buffer, int length) docBroker->broadcastMessage("commandresult: { \"command\": \"savetostorage\", \"success\": true }"); } } + else if (tokens[0] == "clientvisiblearea") + { + int x; + int y; + int width; + int height; + if (tokens.size() != 5 || + !getTokenInteger(tokens[1], "x", x) || + !getTokenInteger(tokens[2], "y", y) || + !getTokenInteger(tokens[3], "width", width) || + !getTokenInteger(tokens[4], "height", height)) + { + sendTextFrame("error: cmd=clientvisiblearea kind=syntax"); + return false; + } + else + { + _clientVisibleArea = Util::Rectangle(x, y, width, height); + return forwardToChild(std::string(buffer, length), docBroker); + } + } + else if (tokens[0] == "setclientpart") + { + 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") + { + int tilePixelWidth, tilePixelHeight, tileTwipWidth, tileTwipHeight; + if (tokens.size() != 5 || + !getTokenInteger(tokens[1], "tilepixelwidth", tilePixelWidth) || + !getTokenInteger(tokens[2], "tilepixelheight", tilePixelHeight) || + !getTokenInteger(tokens[3], "tiletwipwidth", tileTwipWidth) || + !getTokenInteger(tokens[4], "tiletwipheight", tileTwipHeight)) + { + sendTextFrame("error: cmd=clientzoom kind=syntax"); + return false; + } + else + { + _tileWidthPixel = tilePixelWidth; + _tileHeightPixel = tilePixelHeight; + _tileWidthTwips = tileTwipWidth; + _tileHeightTwips = tileTwipHeight; + return forwardToChild(std::string(buffer, length), docBroker); + } + } else { if (tokens[0] == "key") @@ -647,6 +710,14 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt int curPart; return getTokenInteger(tokens[1], "part", curPart); } + else if (tokens[0] == "setpart:" && tokens.size() == 2) + { + int setPart; + if(getTokenInteger(tokens[1], "part", setPart)) + _clientSelectedPart = setPart; + else + return false; + } else if (tokens.size() == 3 && tokens[0] == "saveas:") { bool isConvertTo = static_cast<bool>(_saveAsSocket); diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index df7faa45f..919bc8dbd 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -16,6 +16,7 @@ #include "SenderQueue.hpp" #include "DocumentBroker.hpp" #include <Poco/URI.h> +#include <Rectangle.hpp> class DocumentBroker; @@ -176,6 +177,18 @@ private: uint64_t _keyEvents; SenderQueue<std::shared_ptr<Message>> _senderQueue; + + /// Visible area of the client + Util::Rectangle _clientVisibleArea; + + /// Selected part of the document viewed by the client (no parts in Writer) + int _clientSelectedPart; + + /// Zoom properties of the client + int _tileWidthPixel; + int _tileHeightPixel; + int _tileWidthTwips; + int _tileHeightTwips; }; #endif _______________________________________________ Libreoffice-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
