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 #include +#include class DocumentBroker; + /// Represents a session to a LOOL client, in the WSD process. class ClientSession final : public Session, public std::enable_shared_from_this { @@ -107,6 +109,12 @@ public: /// Set WOPI fileinfo object void setWopiFileInfo(std::unique_ptr& wopiFileInfo) { _wopiFileInfo = std::move(wopiFileInfo); } + boost::optional& 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 _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 tiles; + // Check which newly requested tiles needs rendering. + std::vector tilesNeedsRendering; for (auto& tile : tileCombined.getTiles()) { std::unique_ptr 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 output; - output.reserve(static_cast(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& 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& session) +{ + assert(session->getTilesOnFly() >= 0); + std::unique_lock 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& 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 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 output; + output.reserve(static_cast(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& session) { std::unique_lock 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& session); + void sendRequestedTiles(const std::shared_ptr& session); void cancelTileRequests(const std::shared_ptr& session); void handleTileResponse(const std::vector& payload); void handleDialogPaintResponse(const std::vector& 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= + + Previously sent tile (server -> client) arrived and processed by the client. + Tileid has the next stucture : ::: + downloadas name= id= format= options= Exports the current document to the desired format and returns a download URL