[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