From 087babc2525ddb89e5b31f240a08739d9a3029a9 Mon Sep 17 00:00:00 2001 From: Andrei Karas Date: Mon, 11 Jan 2010 18:52:23 +0200 Subject: Improve chat speed. For improve chat speed this patch add some hacks to BrowserBox class, split big words in ChatTab. Also fix DoS in chat. --- src/gui/widgets/browserbox.cpp | 250 ++++++++++++++++++++++++++++++++++++----- src/gui/widgets/browserbox.h | 20 +++- src/gui/widgets/chattab.cpp | 25 ++++- src/gui/widgets/chattab.h | 2 + 4 files changed, 265 insertions(+), 32 deletions(-) diff --git a/src/gui/widgets/browserbox.cpp b/src/gui/widgets/browserbox.cpp index 610bc153..f87aef57 100644 --- a/src/gui/widgets/browserbox.cpp +++ b/src/gui/widgets/browserbox.cpp @@ -27,6 +27,7 @@ #include #include +#include #include @@ -36,7 +37,10 @@ BrowserBox::BrowserBox(unsigned int mode, bool opaque): mOpaque(opaque), mUseLinksAndUserColors(true), mSelectedLink(-1), - mMaxRows(0) + mMaxRows(0), + mHeight(0), + mWidth(0), + mYStart(0) { setFocusable(true); addMouseListener(this); @@ -206,6 +210,7 @@ void BrowserBox::addRow(const std::string &row) { setHeight(font->getHeight() * mTextRows.size()); } + updateHeight(); } void BrowserBox::clearRows() @@ -215,6 +220,7 @@ void BrowserBox::clearRows() setWidth(0); setHeight(0); mSelectedLink = -1; + updateHeight(); } struct MouseOverLink @@ -248,6 +254,18 @@ void BrowserBox::mouseMoved(gcn::MouseEvent &event) void BrowserBox::draw(gcn::Graphics *graphics) { + gcn::ClipRectangle cr = graphics->getCurrentClipArea(); + mYStart = cr.y - cr.yOffset; + int yEnd = mYStart + cr.height; + if (mYStart < 0) + mYStart = 0; + + if (getWidth() != mWidth) + { + mWidth = getWidth(); + updateHeight(); + } + if (mOpaque) { graphics->setColor(guiPalette->getColor(Palette::BACKGROUND)); @@ -279,14 +297,33 @@ void BrowserBox::draw(gcn::Graphics *graphics) } int x = 0, y = 0; - int wrappedLines = 0; int link = 0; gcn::Font *font = getFont(); + int fontHeight = font->getHeight(); + int fontWidthMinus = font->getWidth("-"); + char const *hyphen = "~"; + int hyphenWidth = font->getWidth(hyphen); + graphics->setColor(guiPalette->getColor(Palette::TEXT)); - for (TextRowIterator i = mTextRows.begin(); i != mTextRows.end(); i++) + const gcn::Color textColor = guiPalette->getColor(Palette::TEXT); + TextRowsHeightIterator h = mTextRowsHeights.begin(); + + for (TextRowIterator i = mTextRows.begin(); + i != mTextRows.end(); + i++, h++) { - const gcn::Color textColor = guiPalette->getColor(Palette::TEXT); + bool hidden = false; + if (y + 50 < mYStart) + { + y += *(h); + continue; + } + else if (y > yEnd) + { + break; + } + gcn::Color selColor = textColor; gcn::Color prevColor = selColor; const std::string row = *(i); @@ -296,13 +333,16 @@ void BrowserBox::draw(gcn::Graphics *graphics) // Check for separator lines if (row.find("---", 0) == 0) { - const int dashWidth = font->getWidth("-"); - for (x = 0; x < getWidth(); x++) + if (!hidden) { - font->drawString(graphics, "-", x, y); - x += dashWidth - 2; + const int dashWidth = fontWidthMinus; + for (x = 0; x < getWidth(); x++) + { + font->drawString(graphics, "-", x, y); + x += dashWidth - 2; + } } - y += font->getHeight(); + y += fontHeight; continue; } @@ -315,7 +355,7 @@ void BrowserBox::draw(gcn::Graphics *graphics) // Wrapped line continuation shall be indented if (wrapped) { - y += font->getHeight(); + y += fontHeight; x = 15; } @@ -323,8 +363,9 @@ void BrowserBox::draw(gcn::Graphics *graphics) if (mUseLinksAndUserColors) end = row.find("##", start + 1); - if (mUseLinksAndUserColors || - (!mUseLinksAndUserColors && (start == 0))) + if (!hidden + && (mUseLinksAndUserColors || + (!mUseLinksAndUserColors && (start == 0)))) { // Check for color change in format "##x", x = [L,P,0..9] if (row.find("##", start) == start && row.size() > start + 2) @@ -339,11 +380,6 @@ void BrowserBox::draw(gcn::Graphics *graphics) } else if (c == '<') { - const int size = mLinks[link].x2 - mLinks[link].x1; - mLinks[link].x1 = x; - mLinks[link].y1 = y; - mLinks[link].x2 = mLinks[link].x1 + size; - mLinks[link].y2 = y + font->getHeight() - 1; link++; prevColor = selColor; selColor = col; @@ -354,6 +390,7 @@ void BrowserBox::draw(gcn::Graphics *graphics) } else { + switch (c) { case '1': selColor = RED; break; @@ -373,9 +410,7 @@ void BrowserBox::draw(gcn::Graphics *graphics) start += 3; if (start == row.size()) - { break; - } } graphics->setColor(selColor); } @@ -385,12 +420,11 @@ void BrowserBox::draw(gcn::Graphics *graphics) std::string part = row.substr(start, len); // Auto wrap mode - if (mMode == AUTO_WRAP && - (x + font->getWidth(part) + 10) > getWidth()) + if (mMode == AUTO_WRAP && getWidth() > 0 + && font->getWidth(part) > 0 + && (x + font->getWidth(part) + 10) > getWidth()) { bool forced = false; - char const *hyphen = "~"; - int hyphenWidth = font->getWidth(hyphen); /* FIXME: This code layout makes it easy to crash remote clients by talking garbage. Forged long utf-8 characters @@ -416,25 +450,183 @@ void BrowserBox::draw(gcn::Graphics *graphics) end--; // And then to the last byte of the previous one part = row.substr(start, end - start + 1); - } while (end > start && (x + font->getWidth(part) + 10) > getWidth()); + } + while (end > start && font->getWidth(part) > 0 + && (x + font->getWidth(part) + 10) > getWidth()); if (forced) { x -= hyphenWidth; // Remove the wrap-notifier accounting - font->drawString(graphics, hyphen, - getWidth() - hyphenWidth, y); + if (y >= mYStart) + { + font->drawString(graphics, hyphen, + getWidth() - hyphenWidth, y); + } end++; // Skip to the next character } else + { end += 2; // Skip to after the space + } wrapped = true; - wrappedLines++; } + font->drawString(graphics, part, x, y); + + if (mMode == AUTO_WRAP && font->getWidth(part) == 0) + break; + x += font->getWidth(part); } - y += font->getHeight(); - setHeight((mTextRows.size() + wrappedLines) * font->getHeight()); + y += fontHeight; } + setHeight(mHeight); +} + +int BrowserBox::calcHeight() +{ + int x = 0, y = 0; + int wrappedLines = 0; + int link = 0; + gcn::Font *font = getFont(); + + int fontHeight = font->getHeight(); + int fontWidthMinus = font->getWidth("-"); + char const *hyphen = "~"; + int hyphenWidth = font->getWidth(hyphen); + + mTextRowsHeights.clear(); + + for (TextRowIterator i = mTextRows.begin(); i != mTextRows.end(); i++) + { + const std::string row = *(i); + bool wrapped = false; + int yStart = y; + x = 0; + + // Check for separator lines + if (row.find("---", 0) == 0) + { + const int dashWidth = fontWidthMinus; + for (x = 0; x < getWidth(); x++) + x += dashWidth - 2; + + y += fontHeight; + continue; + } + + // TODO: Check if we must take texture size limits into account here + // TODO: Check if some of the O(n) calls can be removed + for (std::string::size_type start = 0, end = std::string::npos; + start != std::string::npos; + start = end, end = std::string::npos) + { + // Wrapped line continuation shall be indented + if (wrapped) + { + y += fontHeight; + x = 15; + } + + // "Tokenize" the string at control sequences + if (mUseLinksAndUserColors) + end = row.find("##", start + 1); + + if (mUseLinksAndUserColors || + (!mUseLinksAndUserColors && (start == 0))) + { + // Check for color change in format "##x", x = [L,P,0..9] + if (row.find("##", start) == start && row.size() > start + 2) + { + const char c = row.at(start + 2); + + if (c == '<') + { + const int size = mLinks[link].x2 - mLinks[link].x1; + mLinks[link].x1 = x; + mLinks[link].y1 = y; + mLinks[link].x2 = mLinks[link].x1 + size; + mLinks[link].y2 = y + fontHeight - 1; + link++; + } + start += 3; + + if (start == row.size()) + break; + } + } + + std::string::size_type len = + end == std::string::npos ? end : end - start; + + if (start >= row.length()) + break; + + std::string part = row.substr(start, len); + + // Auto wrap mode + if (mMode == AUTO_WRAP && getWidth() > 0 + && font->getWidth(part) > 0 + && (x + font->getWidth(part) + 10) > getWidth()) + { + bool forced = false; + + /* FIXME: This code layout makes it easy to crash remote + clients by talking garbage. Forged long utf-8 characters + will cause either a buffer underflow in substr or an + infinite loop in the main loop. */ + do + { + if (!forced) + end = row.rfind(' ', end); + + // Check if we have to (stupidly) force-wrap + if (end == std::string::npos || end <= start) + { + forced = true; + end = row.size(); + x += hyphenWidth; // Account for the wrap-notifier + continue; + } + + // Skip to the start of the current character + while ((row[end] & 192) == 128) + end--; + end--; // And then to the last byte of the previous one + + part = row.substr(start, end - start + 1); + } + while (end > start && font->getWidth(part) > 0 + && (x + font->getWidth(part) + 10) > getWidth()); + + if (forced) + { + x -= hyphenWidth; // Remove the wrap-notifier accounting + end++; // Skip to the next character + } + else + { + end += 2; // Skip to after the space + } + + wrapped = true; + wrappedLines++; + } + + if (mMode == AUTO_WRAP && font->getWidth(part) == 0) + break; + + x += font->getWidth(part); + } + y += fontHeight; + mTextRowsHeights.push_back(y - yStart); + } + return (mTextRows.size() + wrappedLines) * fontHeight; +} + +void BrowserBox::updateHeight() +{ + mHeight = calcHeight(); + setHeight(mHeight); } diff --git a/src/gui/widgets/browserbox.h b/src/gui/widgets/browserbox.h index c5ca351d..2823d308 100644 --- a/src/gui/widgets/browserbox.h +++ b/src/gui/widgets/browserbox.h @@ -40,7 +40,8 @@ struct BROWSER_LINK { * A simple browser box able to handle links and forward events to the * parent conteiner. */ -class BrowserBox : public gcn::Widget, public gcn::MouseListener +class BrowserBox : public gcn::Widget, + public gcn::MouseListener { public: /** @@ -88,6 +89,10 @@ class BrowserBox : public gcn::Widget, public gcn::MouseListener */ void clearRows(); +// void setSize(int width, int height); + +// void widgetResized(const gcn::Event &event); + /** * Handles mouse actions. */ @@ -99,6 +104,10 @@ class BrowserBox : public gcn::Widget, public gcn::MouseListener */ void draw(gcn::Graphics *graphics); + void updateHeight(); + +// void widgetResized(const gcn::Event &event); + /** * BrowserBox modes. */ @@ -139,10 +148,16 @@ class BrowserBox : public gcn::Widget, public gcn::MouseListener }; private: + int calcHeight(); + typedef std::list TextRows; typedef TextRows::iterator TextRowIterator; TextRows mTextRows; + typedef std::list TextRowsHeights; + typedef TextRowsHeights::iterator TextRowsHeightIterator; + TextRowsHeights mTextRowsHeights; + typedef std::vector Links; typedef Links::iterator LinkIterator; Links mLinks; @@ -154,6 +169,9 @@ class BrowserBox : public gcn::Widget, public gcn::MouseListener bool mUseLinksAndUserColors; int mSelectedLink; unsigned int mMaxRows; + int mHeight; + int mWidth; + int mYStart; }; #endif diff --git a/src/gui/widgets/chattab.cpp b/src/gui/widgets/chattab.cpp index 6a915e5b..1701cbd3 100644 --- a/src/gui/widgets/chattab.cpp +++ b/src/gui/widgets/chattab.cpp @@ -42,6 +42,8 @@ #include +#define MAX_WORD_SIZE 50 + ChatTab::ChatTab(const std::string &name) : Tab() { setCaption(name); @@ -181,12 +183,12 @@ void ChatTab::chatLog(std::string line, int own, bool ignoreRecord) // at comparison. if (mScrollArea->getVerticalScrollAmount() >= mScrollArea->getVerticalMaxScroll()) { - mTextOutput->addRow(line); + addRow(line); mScrollArea->setVerticalScrollAmount(mScrollArea->getVerticalMaxScroll()); } else { - mTextOutput->addRow(line); + addRow(line); } mScrollArea->logic(); @@ -275,3 +277,22 @@ int ChatTab::getType() const { return INPUT; } + +void ChatTab::addRow(std::string &line) +{ + std::string::size_type idx = 0; + + for (unsigned int f = 0; f < line.length(); f++) + { + if (line.at(f) == ' ') + { + idx = f; + } + else if (f - idx > MAX_WORD_SIZE) + { + line.insert(f, " "); + idx = f; + } + } + mTextOutput->addRow(line); +} diff --git a/src/gui/widgets/chattab.h b/src/gui/widgets/chattab.h index 40a1c1f5..5d8b594a 100644 --- a/src/gui/widgets/chattab.h +++ b/src/gui/widgets/chattab.h @@ -133,6 +133,8 @@ class ChatTab : public Tab virtual void handleCommand(const std::string &msg); + void addRow(std::string &line); + ScrollArea *mScrollArea; BrowserBox *mTextOutput; //Recorder *mRecorder; -- cgit v1.2.3-60-g2f50