aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--doc/ScintillaDoc.html2
-rw-r--r--doc/ScintillaHistory.html3
-rw-r--r--src/Document.cxx4
-rw-r--r--src/Document.h5
-rw-r--r--src/EditView.cxx9
-rw-r--r--src/EditView.h2
-rw-r--r--src/Editor.cxx138
-rw-r--r--src/Editor.h1
-rw-r--r--src/PositionCache.cxx23
-rw-r--r--src/PositionCache.h9
10 files changed, 180 insertions, 16 deletions
diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html
index 30411853e..0b8594f79 100644
--- a/doc/ScintillaDoc.html
+++ b/doc/ScintillaDoc.html
@@ -7982,7 +7982,7 @@ sptr_t CallScintilla(unsigned int iMessage, uptr_t wParam, sptr_t lParam){
<p><b id="SCI_SETLAYOUTTHREADS">SCI_SETLAYOUTTHREADS(int threads)</b><br />
<b id="SCI_GETLAYOUTTHREADS">SCI_GETLAYOUTTHREADS &rarr; int</b><br />
- The time taken to measure text runs on wide lines can be improved by performing the task
+ The time taken to measure text runs on wide lines or when wrapping can be improved by performing the task
concurrently on multiple threads when
<a class="seealso" href="#SCI_SUPPORTSFEATURE">SCI_SUPPORTSFEATURE(SC_SUPPORTS_THREAD_SAFE_MEASURE_WIDTHS)</a>
is available.
diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html
index 7e544789c..9b2d6ccfb 100644
--- a/doc/ScintillaHistory.html
+++ b/doc/ScintillaHistory.html
@@ -587,6 +587,9 @@
Released 8 February 2023.
</li>
<li>
+ Add multithreaded wrap to significantly improve performance of wrapping large files.
+ </li>
+ <li>
More typesafe bindings of *Full APIs in ScintillaCall.
<a href="https://sourceforge.net/p/scintilla/feature-requests/1477/">Feature #1477</a>.
</li>
diff --git a/src/Document.cxx b/src/Document.cxx
index 9d1bf1d3b..4450954b4 100644
--- a/src/Document.cxx
+++ b/src/Document.cxx
@@ -448,6 +448,10 @@ Sci_Position SCI_METHOD Document::LineStart(Sci_Position line) const {
return cb.LineStart(line);
}
+Range Document::LineRange(Sci::Line line) const noexcept {
+ return {cb.LineStart(line), cb.LineStart(line + 1)};
+}
+
bool Document::IsLineStartPosition(Sci::Position position) const {
return LineStart(LineFromPosition(position)) == position;
}
diff --git a/src/Document.h b/src/Document.h
index 9f4811073..72ed532b3 100644
--- a/src/Document.h
+++ b/src/Document.h
@@ -50,6 +50,10 @@ public:
return start == end;
}
+ [[nodiscard]] Sci::Position Length() const noexcept {
+ return (start <= end) ? (end - start) : (start - end);
+ }
+
Sci::Position First() const noexcept {
return (start <= end) ? start : end;
}
@@ -450,6 +454,7 @@ public:
int MarkerNumberFromLine(Sci::Line line, int which) const noexcept;
int MarkerHandleFromLine(Sci::Line line, int which) const noexcept;
Sci_Position SCI_METHOD LineStart(Sci_Position line) const override;
+ [[nodiscard]] Range LineRange(Sci::Line line) const noexcept;
bool IsLineStartPosition(Sci::Position position) const;
Sci_Position SCI_METHOD LineEnd(Sci_Position line) const override;
Sci::Position LineEndPosition(Sci::Position position) const;
diff --git a/src/EditView.cxx b/src/EditView.cxx
index 259ebf30d..09a20eda3 100644
--- a/src/EditView.cxx
+++ b/src/EditView.cxx
@@ -395,7 +395,7 @@ void LayoutSegments(IPositionCache *pCache,
* Copy the given @a line and its styles from the document into local arrays.
* Also determine the x position at which each character starts.
*/
-void EditView::LayoutLine(const EditModel &model, Surface *surface, const ViewStyle &vstyle, LineLayout *ll, int width) {
+void EditView::LayoutLine(const EditModel &model, Surface *surface, const ViewStyle &vstyle, LineLayout *ll, int width, bool callerMultiThreaded) {
if (!ll)
return;
const Sci::Line line = ll->LineNumber();
@@ -491,7 +491,7 @@ void EditView::LayoutLine(const EditModel &model, Surface *surface, const ViewSt
const size_t threadsForLength = std::max(1, numCharsInLine / bytesPerLayoutThread);
size_t threads = std::min<size_t>({ segments.size(), threadsForLength, maxLayoutThreads });
- if (!surface->SupportsFeature(Supports::ThreadSafeMeasureWidths)) {
+ if (!surface->SupportsFeature(Supports::ThreadSafeMeasureWidths) || callerMultiThreaded) {
threads = 1;
}
@@ -499,6 +499,7 @@ void EditView::LayoutLine(const EditModel &model, Surface *surface, const ViewSt
const bool textUnicode = CpUtf8 == model.pdoc->dbcsCodePage;
const bool multiThreaded = threads > 1;
+ const bool multiThreadedContext = multiThreaded || callerMultiThreaded;
IPositionCache *pCache = posCache.get();
// If only 1 thread needed then use the main thread, else spin up multiple
@@ -508,8 +509,8 @@ void EditView::LayoutLine(const EditModel &model, Surface *surface, const ViewSt
for (size_t th = 0; th < threads; th++) {
// Find relative positions of everything except for tabs
std::future<void> fut = std::async(policy,
- [pCache, surface, &vstyle, &ll, &segments, &nextIndex, textUnicode, multiThreaded]() {
- LayoutSegments(pCache, surface, vstyle, ll, segments, nextIndex, textUnicode, multiThreaded);
+ [pCache, surface, &vstyle, &ll, &segments, &nextIndex, textUnicode, multiThreadedContext]() {
+ LayoutSegments(pCache, surface, vstyle, ll, segments, nextIndex, textUnicode, multiThreadedContext);
});
futures.push_back(std::move(fut));
}
diff --git a/src/EditView.h b/src/EditView.h
index affb4663e..1e28d25c5 100644
--- a/src/EditView.h
+++ b/src/EditView.h
@@ -119,7 +119,7 @@ public:
std::shared_ptr<LineLayout> RetrieveLineLayout(Sci::Line lineNumber, const EditModel &model);
void LayoutLine(const EditModel &model, Surface *surface, const ViewStyle &vstyle,
- LineLayout *ll, int width);
+ LineLayout *ll, int width, bool callerMultiThreaded=false);
static void UpdateBidiData(const EditModel &model, const ViewStyle &vstyle, LineLayout *ll);
diff --git a/src/Editor.cxx b/src/Editor.cxx
index d64a09deb..30a9c9786 100644
--- a/src/Editor.cxx
+++ b/src/Editor.cxx
@@ -25,6 +25,9 @@
#include <iterator>
#include <memory>
#include <chrono>
+#include <atomic>
+#include <thread>
+#include <future>
#include "ScintillaTypes.h"
#include "ScintillaMessages.h"
@@ -1473,6 +1476,130 @@ bool Editor::WrapOneLine(Surface *surface, Sci::Line lineToWrap) {
return pcs->SetHeight(lineToWrap, linesWrapped);
}
+namespace {
+
+// Lines less than lengthToMultiThread are laid out in blocks in parallel.
+// Longer lines are multi-threaded inside LayoutLine.
+// This allows faster processing when lines differ greatly in length and thus time to lay out.
+constexpr Sci::Position lengthToMultiThread = 4000;
+
+}
+
+bool Editor::WrapBlock(Surface *surface, Sci::Line lineToWrap, Sci::Line lineToWrapEnd) {
+
+ const size_t linesBeingWrapped = static_cast<size_t>(lineToWrapEnd - lineToWrap);
+
+ std::vector<int> linesAfterWrap(linesBeingWrapped);
+
+ size_t threads = std::min<size_t>({ linesBeingWrapped, view.maxLayoutThreads });
+ if (!surface->SupportsFeature(Supports::ThreadSafeMeasureWidths)) {
+ threads = 1;
+ }
+
+ const bool multiThreaded = threads > 1;
+
+ ElapsedPeriod epWrapping;
+
+ // Wrap all the short lines in multiple threads
+
+ // If only 1 thread needed then use the main thread, else spin up multiple
+ const std::launch policy = multiThreaded ? std::launch::async : std::launch::deferred;
+
+ std::atomic<size_t> nextIndex = 0;
+
+ // Lines that are less likely to be re-examined should not be read from or written to the cache.
+ const SignificantLines significantLines {
+ pdoc->SciLineFromPosition(sel.MainCaret()),
+ topLine,
+ LinesOnScreen() + 1,
+ view.llc.GetLevel(),
+ };
+
+ // Protect the line layout cache from being accessed from multiple threads simultaneously
+ std::mutex mutexRetrieve;
+
+ std::vector<std::future<void>> futures;
+ for (size_t th = 0; th < threads; th++) {
+ std::future<void> fut = std::async(policy,
+ [=, &surface, &nextIndex, &linesAfterWrap, &mutexRetrieve]() {
+ // llTemporary is reused for non-significant lines, avoiding allocation costs.
+ std::shared_ptr<LineLayout> llTemporary = std::make_shared<LineLayout>(-1, 200);
+ while (true) {
+ const size_t i = nextIndex.fetch_add(1, std::memory_order_acq_rel);
+ if (i >= linesBeingWrapped) {
+ break;
+ }
+ const Sci::Line lineNumber = lineToWrap + i;
+ const Range rangeLine = pdoc->LineRange(lineNumber);
+ const Sci::Position lengthLine = rangeLine.Length();
+ if (lengthLine < lengthToMultiThread) {
+ std::shared_ptr<LineLayout> ll;
+ if (significantLines.LineMayCache(lineNumber)) {
+ std::lock_guard<std::mutex> guard(mutexRetrieve);
+ ll = view.RetrieveLineLayout(lineNumber, *this);
+ } else {
+ ll = llTemporary;
+ ll->ReSet(lineNumber, lengthLine);
+ }
+ view.LayoutLine(*this, surface, vs, ll.get(), wrapWidth, multiThreaded);
+ linesAfterWrap[i] = ll->lines;
+ }
+ }
+ });
+ futures.push_back(std::move(fut));
+ }
+ for (const std::future<void> &f : futures) {
+ f.wait();
+ }
+ // End of multiple threads
+
+ // Multiply duration by number of threads to produce (near) equivalence to duration if single threaded
+ const double durationShortLines = epWrapping.Duration(true);
+ const double durationShortLinesThreads = durationShortLines * threads;
+
+ // Wrap all the long lines in the main thread.
+ // LayoutLine may then multi-thread over segments in each line.
+
+ std::shared_ptr<LineLayout> llLarge = std::make_shared<LineLayout>(-1, 200);
+ for (size_t indexLarge = 0; indexLarge < linesBeingWrapped; indexLarge++) {
+ const Sci::Line lineNumber = lineToWrap + indexLarge;
+ const Range rangeLine = pdoc->LineRange(lineNumber);
+ const Sci::Position lengthLine = rangeLine.Length();
+ if (lengthLine >= lengthToMultiThread) {
+ std::shared_ptr<LineLayout> ll;
+ if (significantLines.LineMayCache(lineNumber)) {
+ ll = view.RetrieveLineLayout(lineNumber, *this);
+ } else {
+ ll = llLarge;
+ ll->ReSet(lineNumber, lengthLine);
+ }
+ view.LayoutLine(*this, surface, vs, ll.get(), wrapWidth);
+ linesAfterWrap[indexLarge] = ll->lines;
+ }
+ }
+
+ const double durationLongLines = epWrapping.Duration();
+ const size_t bytesBeingWrapped = pdoc->LineStart(lineToWrap + linesBeingWrapped) - pdoc->LineStart(lineToWrap);
+
+ size_t wrapsDone = 0;
+
+ for (size_t i = 0; i < linesBeingWrapped; i++) {
+ const Sci::Line lineNumber = lineToWrap + i;
+ int linesWrapped = linesAfterWrap[i];
+ if (vs.annotationVisible != AnnotationVisible::Hidden) {
+ linesWrapped += pdoc->AnnotationLines(lineNumber);
+ }
+ if (pcs->SetHeight(lineNumber, linesWrapped)) {
+ wrapsDone++;
+ }
+ wrapPending.Wrapped(lineNumber);
+ }
+
+ durationWrapOneByte.AddSample(bytesBeingWrapped, durationShortLinesThreads + durationLongLines);
+
+ return wrapsDone > 0;
+}
+
// Perform wrapping for a subset of the lines needing wrapping.
// wsAll: wrap all lines which need wrapping in this single call
// wsVisible: wrap currently visible lines
@@ -1554,16 +1681,7 @@ bool Editor::WrapLines(WrapScope ws) {
if (surface) {
//Platform::DebugPrintf("Wraplines: scope=%0d need=%0d..%0d perform=%0d..%0d\n", ws, wrapPending.start, wrapPending.end, lineToWrap, lineToWrapEnd);
- const size_t bytesBeingWrapped = pdoc->LineStart(lineToWrapEnd) - pdoc->LineStart(lineToWrap);
- ElapsedPeriod epWrapping;
- while (lineToWrap < lineToWrapEnd) {
- if (WrapOneLine(surface, lineToWrap)) {
- wrapOccurred = true;
- }
- wrapPending.Wrapped(lineToWrap);
- lineToWrap++;
- }
- durationWrapOneByte.AddSample(bytesBeingWrapped, epWrapping.Duration());
+ wrapOccurred = WrapBlock(surface, lineToWrap, lineToWrapEnd);
goodTopLine = pcs->DisplayFromDoc(lineDocTop) + std::min(
subLineTop, static_cast<Sci::Line>(pcs->GetHeight(lineDocTop)-1));
diff --git a/src/Editor.h b/src/Editor.h
index 61e0dd5b0..f9d938685 100644
--- a/src/Editor.h
+++ b/src/Editor.h
@@ -400,6 +400,7 @@ protected: // ScintillaBase subclass needs access to much of Editor
bool Wrapping() const noexcept;
void NeedWrapping(Sci::Line docLineStart=0, Sci::Line docLineEnd=WrapPending::lineLarge);
bool WrapOneLine(Surface *surface, Sci::Line lineToWrap);
+ bool WrapBlock(Surface *surface, Sci::Line lineToWrap, Sci::Line lineToWrapEnd);
enum class WrapScope {wsAll, wsVisible, wsIdle};
bool WrapLines(WrapScope ws);
void LinesJoin();
diff --git a/src/PositionCache.cxx b/src/PositionCache.cxx
index 30d76b1d3..9f4e4edc7 100644
--- a/src/PositionCache.cxx
+++ b/src/PositionCache.cxx
@@ -102,6 +102,13 @@ void LineLayout::Resize(int maxLineLength_) {
}
}
+void LineLayout::ReSet(Sci::Line lineNumber_, Sci::Position maxLineLength_) {
+ lineNumber = lineNumber_;
+ Resize(static_cast<int>(maxLineLength_));
+ lines = 0;
+ Invalidate(ValidLevel::invalid);
+}
+
void LineLayout::EnsureBidiData() {
if (!bidiData) {
bidiData = std::make_unique<BidiData>();
@@ -114,6 +121,7 @@ void LineLayout::Free() noexcept {
styles.reset();
positions.reset();
lineStarts.reset();
+ lenLineStarts = 0;
bidiData.reset();
}
@@ -446,6 +454,21 @@ XYPOSITION ScreenLine::TabPositionAfter(XYPOSITION xPosition) const {
return (std::floor((xPosition + TabWidthMinimumPixels()) / TabWidth()) + 1) * TabWidth();
}
+bool SignificantLines::LineMayCache(Sci::Line line) const noexcept {
+ switch (level) {
+ case LineCache::None:
+ return false;
+ case LineCache::Caret:
+ return line == lineCaret;
+ case LineCache::Page:
+ return (abs(line - lineCaret) < linesOnScreen) ||
+ ((line >= lineTop) && (line <= (lineTop + linesOnScreen)));
+ case LineCache::Document:
+ default:
+ return true;
+ }
+}
+
LineLayoutCache::LineLayoutCache() :
level(LineCache::None),
allInvalidated(false), styleClock(-1) {
diff --git a/src/PositionCache.h b/src/PositionCache.h
index 0add8319a..e610ac879 100644
--- a/src/PositionCache.h
+++ b/src/PositionCache.h
@@ -83,6 +83,7 @@ public:
void operator=(LineLayout &&) = delete;
virtual ~LineLayout();
void Resize(int maxLineLength_);
+ void ReSet(Sci::Line lineNumber_, Sci::Position maxLineLength_);
void EnsureBidiData();
void Free() noexcept;
void ClearPositions();
@@ -140,6 +141,14 @@ struct ScreenLine : public IScreenLine {
XYPOSITION TabPositionAfter(XYPOSITION xPosition) const override;
};
+struct SignificantLines {
+ Sci::Line lineCaret;
+ Sci::Line lineTop;
+ Sci::Line linesOnScreen;
+ Scintilla::LineCache level;
+ bool LineMayCache(Sci::Line line) const noexcept;
+};
+
/**
*/
class LineLayoutCache {