[Libreoffice-commits] online.git: loolwsd/ClientSession.cpp loolwsd/DocumentBroker.cpp loolwsd/DocumentBroker.hpp loolwsd/LOOLKit.cpp

Ashod Nakashian ashod.nakashian at collabora.co.uk
Mon May 23 01:50:12 UTC 2016


 loolwsd/ClientSession.cpp  |   66 +---------------
 loolwsd/DocumentBroker.cpp |  106 ++++++++++++++++++++++++++
 loolwsd/DocumentBroker.hpp |    3 
 loolwsd/LOOLKit.cpp        |  178 ++++++++++++++++-----------------------------
 4 files changed, 182 insertions(+), 171 deletions(-)

New commits:
commit 114a6bc373a8bd8ea1af3144b8b500f92060f2d0
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sun May 22 11:45:28 2016 -0400

    loolwsd: combined tile rendering
    
    Tiles can be rendered in large batches and sent back
    as combined payloads, all reduce latency and overhead
    significantly.
    
    Initial tests show a reduction in total latency by 2x.
    This without sending tiles back to the client in combined
    form, which will reduce latency further.
    
    Change-Id: Iee06503f2a6c741fadb2c890266ea514c809c0dc
    Reviewed-on: https://gerrit.libreoffice.org/25339
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>

diff --git a/loolwsd/ClientSession.cpp b/loolwsd/ClientSession.cpp
index 7158a97..1fb1ec7 100644
--- a/loolwsd/ClientSession.cpp
+++ b/loolwsd/ClientSession.cpp
@@ -337,69 +337,15 @@ bool ClientSession::sendTile(const char * /*buffer*/, int /*length*/, StringToke
 
 bool ClientSession::sendCombinedTiles(const char* /*buffer*/, int /*length*/, StringTokenizer& tokens)
 {
-    int part, pixelWidth, pixelHeight, tileWidth, tileHeight;
-    std::string tilePositionsX, tilePositionsY;
-    if (tokens.count() < 8 ||
-        !getTokenInteger(tokens[1], "part", part) ||
-        !getTokenInteger(tokens[2], "width", pixelWidth) ||
-        !getTokenInteger(tokens[3], "height", pixelHeight) ||
-        !getTokenString (tokens[4], "tileposx", tilePositionsX) ||
-        !getTokenString (tokens[5], "tileposy", tilePositionsY) ||
-        !getTokenInteger(tokens[6], "tilewidth", tileWidth) ||
-        !getTokenInteger(tokens[7], "tileheight", tileHeight))
-    {
-        return sendTextFrame("error: cmd=tilecombine kind=syntax");
-    }
-
-    if (part < 0 || pixelWidth <= 0 || pixelHeight <= 0 ||
-        tileWidth <= 0 || tileHeight <= 0 ||
-        tilePositionsX.empty() || tilePositionsY.empty())
-    {
-        return sendTextFrame("error: cmd=tilecombine kind=invalid");
-    }
-
-    std::string reqTimestamp;
-    size_t index = 8;
-    if (tokens.count() > index && tokens[index].find("timestamp") == 0)
-    {
-        getTokenString(tokens[index], "timestamp", reqTimestamp);
-        ++index;
-    }
-
-    int id = -1;
-    if (tokens.count() > index && tokens[index].find("id") == 0)
-    {
-        getTokenInteger(tokens[index], "id", id);
-        ++index;
-    }
-
-    StringTokenizer positionXtokens(tilePositionsX, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-    StringTokenizer positionYtokens(tilePositionsY, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-
-    size_t numberOfPositions = positionYtokens.count();
-
-    // check that number of positions for X and Y is the same
-    if (numberOfPositions != positionXtokens.count())
+    try
     {
-        return sendTextFrame("error: cmd=tilecombine kind=invalid");
+        auto tileCombined = TileCombined::parse(tokens);
+        _docBroker->handleTileCombinedRequest(tileCombined, shared_from_this());
     }
-
-    for (size_t i = 0; i < numberOfPositions; ++i)
+    catch (const std::exception& exc)
     {
-        int x = 0;
-        if (!stringToInteger(positionXtokens[i], x))
-        {
-            return sendTextFrame("error: cmd=tilecombine kind=syntax");
-        }
-
-        int y = 0;
-        if (!stringToInteger(positionYtokens[i], y))
-        {
-            return sendTextFrame("error: cmd=tilecombine kind=syntax");
-        }
-
-        const TileDesc tile(part, pixelWidth, pixelHeight, x, y, tileWidth, tileHeight);
-        _docBroker->handleTileRequest(tile, shared_from_this());
+        Log::error(std::string("Failed to process tilecombine command: ") + exc.what() + ".");
+        return sendTextFrame("error: cmd=tile kind=invalid");
     }
 
     return true;
diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp
index 4c69a36..62dfe60 100644
--- a/loolwsd/DocumentBroker.cpp
+++ b/loolwsd/DocumentBroker.cpp
@@ -403,6 +403,10 @@ bool DocumentBroker::handleInput(const std::vector<char>& payload)
     {
         handleTileResponse(payload);
     }
+    else if (command == "tilecombine:")
+    {
+       handleTileCombinedResponse(payload);
+    }
 
     return true;
 }
@@ -453,6 +457,69 @@ void DocumentBroker::handleTileRequest(const TileDesc& tile,
     _childProcess->getWebSocket()->sendFrame(request.data(), request.size());
 }
 
+void DocumentBroker::handleTileCombinedRequest(TileCombined& tileCombined,
+                                               const std::shared_ptr<ClientSession>& session)
+{
+    Log::trace() << "TileCombined request for " << tileCombined.serialize() << Log::end;
+
+    std::unique_lock<std::mutex> lock(_mutex);
+
+    // Satisfy as many tiles from the cache.
+    auto& tiles = tileCombined.getTiles();
+    int i = tiles.size();
+    while (--i >= 0)
+    {
+        const auto& tile = tiles[i];
+        std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(tile);
+        if (cachedTile)
+        {
+            //TODO: Combine.
+#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(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);
+            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);
+            cachedTile->close();
+
+            session->sendBinaryFrame(output.data(), output.size());
+
+            // Remove.
+            tiles.erase(tiles.begin() + i);
+        }
+        else if (tileCache().isTileBeingRenderedIfSoSubscribe(tile, session))
+        {
+            // Skip.
+            tiles.erase(tiles.begin() + i);
+        }
+    }
+
+    if (tiles.empty())
+    {
+        // Done.
+        return;
+    }
+
+    const auto tileMsg = tileCombined.serialize();
+    Log::debug() << "TileCombined residual request for " << tileMsg << Log::end;
+
+    // Forward to child to render.
+    const std::string request = "tilecombine " + tileMsg;
+    _childProcess->getWebSocket()->sendFrame(request.data(), request.size());
+}
+
 void DocumentBroker::handleTileResponse(const std::vector<char>& payload)
 {
     const std::string firstLine = getFirstLine(payload);
@@ -482,6 +549,45 @@ void DocumentBroker::handleTileResponse(const std::vector<char>& payload)
     }
 }
 
+void DocumentBroker::handleTileCombinedResponse(const std::vector<char>& payload)
+{
+    const std::string firstLine = getFirstLine(payload);
+    Log::debug("Handling tile combined: " + firstLine);
+
+    try
+    {
+        auto tileCombined = TileCombined::parse(firstLine);
+        const auto buffer = payload.data();
+        const auto length = payload.size();
+        auto offset = firstLine.size() + 1;
+
+        if (firstLine.size() < static_cast<std::string::size_type>(length) - 1)
+        {
+            for (const auto& tile : tileCombined.getTiles())
+            {
+                tileCache().saveTile(tile, buffer + offset, tile.getImgSize());
+                tileCache().notifyAndRemoveSubscribers(tile);
+                offset += tile.getImgSize();
+            }
+        }
+        else
+        {
+            Log::error() << "Render request failed for " << firstLine << Log::end;
+            std::unique_lock<std::mutex> tileBeingRenderedLock(tileCache().getTilesBeingRenderedLock());
+            for (const auto& tile : tileCombined.getTiles())
+            {
+                tileCache().forgetTileBeingRendered(tile);
+            }
+        }
+    }
+    catch (const std::exception& exc)
+    {
+        Log::error("Failed to process tile response [" + firstLine + "]: " + exc.what() + ".");
+        //FIXME: Return error.
+        //sendTextFrame("error: cmd=tile kind=syntax");
+    }
+}
+
 bool DocumentBroker::canDestroy()
 {
     std::unique_lock<std::mutex> lock(_mutex);
diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp
index 3fa7def..b722d76 100644
--- a/loolwsd/DocumentBroker.hpp
+++ b/loolwsd/DocumentBroker.hpp
@@ -204,8 +204,11 @@ public:
 
     void handleTileRequest(const TileDesc& tile,
                            const std::shared_ptr<ClientSession>& session);
+    void handleTileCombinedRequest(TileCombined& tileCombined,
+                                   const std::shared_ptr<ClientSession>& session);
 
     void handleTileResponse(const std::vector<char>& payload);
+    void handleTileCombinedResponse(const std::vector<char>& payload);
 
     // Called when the last view is going out.
     bool canDestroy();
diff --git a/loolwsd/LOOLKit.cpp b/loolwsd/LOOLKit.cpp
index f2ba282..f594fa8 100644
--- a/loolwsd/LOOLKit.cpp
+++ b/loolwsd/LOOLKit.cpp
@@ -52,6 +52,7 @@
 #include "Log.hpp"
 #include "Png.hpp"
 #include "QueueHandler.hpp"
+#include "Rectangle.hpp"
 #include "TileDesc.hpp"
 #include "Unit.hpp"
 #include "UserMessages.hpp"
@@ -617,79 +618,21 @@ public:
         ws->sendFrame(output.data(), length, WebSocket::FRAME_BINARY);
     }
 
-    void sendCombinedTiles(const char* /*buffer*/, int /*length*/, StringTokenizer& /*tokens*/)
+    void renderCombinedTiles(StringTokenizer& tokens, const std::shared_ptr<Poco::Net::WebSocket>& ws)
     {
-        // This is unnecessary at this point, since the DocumentBroker will send us individual
-        // tile requests (i.e. it breaks tilecombine requests).
-        // So unless DocumentBroker combines them again, there is no point in having this here.
-        // In fact, we probably want to remove this, since we always want to render individual
-        // tiles so that we can fetch them separately in the future.
-#if 0
-        int part, pixelWidth, pixelHeight, tileWidth, tileHeight;
-        std::string tilePositionsX, tilePositionsY;
-        std::string reqTimestamp;
-
-        if (tokens.count() < 8 ||
-            !getTokenInteger(tokens[1], "part", part) ||
-            !getTokenInteger(tokens[2], "width", pixelWidth) ||
-            !getTokenInteger(tokens[3], "height", pixelHeight) ||
-            !getTokenString (tokens[4], "tileposx", tilePositionsX) ||
-            !getTokenString (tokens[5], "tileposy", tilePositionsY) ||
-            !getTokenInteger(tokens[6], "tilewidth", tileWidth) ||
-            !getTokenInteger(tokens[7], "tileheight", tileHeight))
-        {
-            //sendTextFrame("error: cmd=tilecombine kind=syntax");
-            return;
-        }
-
-        if (part < 0 || pixelWidth <= 0 || pixelHeight <= 0
-           || tileWidth <= 0 || tileHeight <= 0
-           || tilePositionsX.empty() || tilePositionsY.empty())
-        {
-            //sendTextFrame("error: cmd=tilecombine kind=invalid");
-            return;
-        }
-
-        if (tokens.count() > 8)
-            getTokenString(tokens[8], "timestamp", reqTimestamp);
-
-        bool makeSlow = delayAndRewritePart(part);
+        auto tileCombined = TileCombined::parse(tokens);
+        auto& tiles = tileCombined.getTiles();
 
         Util::Rectangle renderArea;
+        std::vector<Util::Rectangle> tileRecs;
+        tileRecs.reserve(tiles.size());
 
-        StringTokenizer positionXtokens(tilePositionsX, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-        StringTokenizer positionYtokens(tilePositionsY, ",", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-
-        size_t numberOfPositions = positionYtokens.count();
-        // check that number of positions for X and Y is the same
-        if (numberOfPositions != positionYtokens.count())
+        for (auto& tile : tiles)
         {
-            sendTextFrame("error: cmd=tilecombine kind=invalid");
-            return;
-        }
-
-        std::vector<Util::Rectangle> tiles;
-        tiles.reserve(numberOfPositions);
+            Util::Rectangle rectangle(tile.getTilePosX(), tile.getTilePosY(),
+                                      tileCombined.getTileWidth(), tileCombined.getTileHeight());
 
-        for (size_t i = 0; i < numberOfPositions; ++i)
-        {
-            int x = 0;
-            if (!stringToInteger(positionXtokens[i], x))
-            {
-                sendTextFrame("error: cmd=tilecombine kind=syntax");
-                return;
-            }
-
-            int y = 0;
-            if (!stringToInteger(positionYtokens[i], y))
-            {
-                sendTextFrame("error: cmd=tilecombine kind=syntax");
-                return;
-            }
-
-            Util::Rectangle rectangle(x, y, tileWidth, tileHeight);
-
-            if (tiles.empty())
+            if (tileRecs.empty())
             {
                 renderArea = rectangle;
             }
@@ -698,71 +641,77 @@ public:
                 renderArea.extend(rectangle);
             }
 
-            tiles.push_back(rectangle);
+            tileRecs.push_back(rectangle);
         }
 
-        LibreOfficeKitTileMode mode = static_cast<LibreOfficeKitTileMode>(_loKitDocument->pClass->getTileMode(_loKitDocument));
-
-        int tilesByX = renderArea.getWidth() / tileWidth;
-        int tilesByY = renderArea.getHeight() / tileHeight;
+        const int tilesByX = renderArea.getWidth() / tileCombined.getTileWidth();
+        const int tilesByY = renderArea.getHeight() / tileCombined.getTileHeight();
 
-        int pixmapWidth = tilesByX * pixelWidth;
-        int pixmapHeight = tilesByY * pixelHeight;
+        const int pixmapWidth = tilesByX * tileCombined.getWidth();
+        const int pixmapHeight = tilesByY * tileCombined.getHeight();
 
         const size_t pixmapSize = 4 * pixmapWidth * pixmapHeight;
 
         std::vector<unsigned char> pixmap(pixmapSize, 0);
 
         Timestamp timestamp;
-        _loKitDocument->pClass->paintPartTile(_loKitDocument, pixmap.data(), part,
-                                              pixmapWidth, pixmapHeight,
-                                              renderArea.getLeft(), renderArea.getTop(),
-                                              renderArea.getWidth(), renderArea.getHeight());
+        _loKitDocument->paintPartTile(pixmap.data(), tileCombined.getPart(),
+                                      pixmapWidth, pixmapHeight,
+                                      renderArea.getLeft(), renderArea.getTop(),
+                                      renderArea.getWidth(), renderArea.getHeight());
 
         Log::debug() << "paintTile (combined) called, tile at [" << renderArea.getLeft() << ", " << renderArea.getTop() << "]"
-                    << " (" << renderArea.getWidth() << ", " << renderArea.getHeight() << ") rendered in "
-                    << double(timestamp.elapsed())/1000 <<  "ms" << Log::end;
-
-        for (Util::Rectangle& tileRect : tiles)
-        {
-            std::string response = "tile: part=" + std::to_string(part) +
-                                   " width=" + std::to_string(pixelWidth) +
-                                   " height=" + std::to_string(pixelHeight) +
-                                   " tileposx=" + std::to_string(tileRect.getLeft()) +
-                                   " tileposy=" + std::to_string(tileRect.getTop()) +
-                                   " tilewidth=" + std::to_string(tileWidth) +
-                                   " tileheight=" + std::to_string(tileHeight);
-
-            if (reqTimestamp != "")
-                response += " timestamp=" + reqTimestamp;
-
-#if ENABLE_DEBUG
-            response += " renderid=" + Util::UniqueId();
-#endif
-
-            response += "\n";
-
-            std::vector<char> output;
-            output.reserve(pixelWidth * pixelHeight * 4 + response.size());
-            output.resize(response.size());
+                     << " (" << renderArea.getWidth() << ", " << renderArea.getHeight() << ") rendered in "
+                     << double(timestamp.elapsed())/1000 <<  " ms." << Log::end;
 
-            std::copy(response.begin(), response.end(), output.begin());
 
-            int positionX = (tileRect.getLeft() - renderArea.getLeft()) / tileWidth;
-            int positionY = (tileRect.getTop() - renderArea.getTop())  / tileHeight;
+        std::vector<char> output;
+        output.reserve(pixmapWidth * pixmapHeight * 4);
 
-            if (!Util::encodeSubBufferToPNG(pixmap.data(), positionX * pixelWidth, positionY * pixelHeight, pixelWidth, pixelHeight, pixmapWidth, pixmapHeight, output, mode))
+        const auto mode = static_cast<LibreOfficeKitTileMode>(_loKitDocument->getTileMode());
+        size_t tileIndex = 0;
+        for (Util::Rectangle& tileRect : tileRecs)
+        {
+            const int positionX = (tileRect.getLeft() - renderArea.getLeft()) / tileCombined.getTileWidth();
+            const int positionY = (tileRect.getTop() - renderArea.getTop())  / tileCombined.getTileHeight();
+
+            const auto oldSize = output.size();
+            const auto pixelWidth = tileCombined.getWidth();
+            const auto pixelHeight = tileCombined.getHeight();
+            if (!png::encodeSubBufferToPNG(pixmap.data(), positionX * pixelWidth, positionY * pixelHeight,
+                                           pixelWidth, pixelHeight, pixmapWidth, pixmapHeight, output, mode))
             {
-                sendTextFrame("error: cmd=tile kind=failure");
+                //FIXME: Return error.
+                //sendTextFrame("error: cmd=tile kind=failure");
+                Log::error("Failed to encode tile into PNG.");
                 return;
             }
 
-            sendBinaryFrame(output.data(), output.size());
+            const auto imgSize = output.size() - oldSize;
+            Log::trace() << "Encoded tile #" << tileIndex << " in " << imgSize << " bytes." << Log::end;
+            tiles[tileIndex++].setImgSize(imgSize);
         }
 
-        if (makeSlow)
-            delay();
+#if ENABLE_DEBUG
+        const auto tileMsg = tileCombined.serialize("tilecombine:") + " renderid=" + Util::UniqueId() + "\n";
+#else
+        const auto tileMsg = tileCombined.serialize("tilecombine:") + "\n";
 #endif
+        Log::trace("Sending back painted tiles for " + tileMsg);
+
+        std::vector<char> response;
+        response.resize(tileMsg.size() + output.size());
+        std::copy(tileMsg.begin(), tileMsg.end(), response.begin());
+        std::copy(output.begin(), output.end(), response.begin() + tileMsg.size());
+
+        const auto length = response.size();
+        if (length > SMALL_MESSAGE_SIZE)
+        {
+            const std::string nextmessage = "nextmessage: size=" + std::to_string(length);
+            ws->sendFrame(nextmessage.data(), nextmessage.size());
+        }
+
+        ws->sendFrame(response.data(), length, WebSocket::FRAME_BINARY);
     }
 
 private:
@@ -1254,6 +1203,13 @@ void lokit_main(const std::string& childRoot,
                             document->renderTile(tokens, ws);
                         }
                     }
+                    else if (tokens[0] == "tilecombine")
+                    {
+                        if (document)
+                        {
+                            document->renderCombinedTiles(tokens, ws);
+                        }
+                    }
                     else if (document && document->canDiscard())
                     {
                         TerminationFlag = true;


More information about the Libreoffice-commits mailing list