mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-01 10:40:20 +03:00
Optimize bulk document insertion and IME location updates
Build multiline TextDocumentLine values once and move-insert them as a range, while batching client notifications under one lock. Queue InputMethod location changes globally and flush only the latest rectangle at a configurable 50 ms interval. Keep backend calls on the main thread and avoid requests from unfocused text widgets. Use EEPP_IME_LOCATION_UPDATE_INTERVAL_MS to override the interval; 0 flushes once per rendered frame. Add large multiline insertion coverage and document the remaining NewMultiFileDiffViewer performance work. Enable Haiku x86_64 nightly builds
This commit is contained in:
@@ -443,40 +443,40 @@ jobs:
|
||||
files: |
|
||||
projects/freebsd/ecode/ecode-freebsd-${{ env.INSTALL_REF }}-x86_64.tar.gz
|
||||
|
||||
# build_haiku_x86_64:
|
||||
# name: Haiku x86_64 Nightly
|
||||
# needs: release
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout Code
|
||||
# uses: actions/checkout@v6
|
||||
# with: { fetch-depth: 0, submodules: 'recursive' }
|
||||
# - name: Set Environment Variables
|
||||
# run: |
|
||||
# echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
|
||||
# echo "RARCH=$(uname -m)" >> "$GITHUB_ENV"
|
||||
# - uses: vmactions/haiku-vm@v1
|
||||
# env:
|
||||
# INSTALL_REF: ${{ needs.release.outputs.version }}
|
||||
# with:
|
||||
# envs: 'INSTALL_REF'
|
||||
# usesh: true
|
||||
# mem: 8192
|
||||
# disable-cache: true
|
||||
# prepare: |
|
||||
# pkgman install -y libsdl2 libsdl2_devel git gcc premake5
|
||||
# run: |
|
||||
# git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
# bash projects/scripts/patch_commit_number.sh
|
||||
# sh projects/haiku/ecode/build.app.sh --version ${{ env.INSTALL_REF }}
|
||||
# - name: Upload Files
|
||||
# uses: softprops/action-gh-release@v3
|
||||
# with:
|
||||
# tag_name: ${{ needs.release.outputs.version }}
|
||||
# draft: false
|
||||
# prerelease: true
|
||||
# files: |
|
||||
# projects/haiku/ecode/ecode-haiku-${{ env.INSTALL_REF }}-x86_64.tar.gz
|
||||
build_haiku_x86_64:
|
||||
name: Haiku x86_64 Nightly
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v6
|
||||
with: { fetch-depth: 0, submodules: 'recursive' }
|
||||
- name: Set Environment Variables
|
||||
run: |
|
||||
echo "INSTALL_REF=${{ needs.release.outputs.version }}" >> "$GITHUB_ENV"
|
||||
echo "RARCH=$(uname -m)" >> "$GITHUB_ENV"
|
||||
- uses: vmactions/haiku-vm@v1
|
||||
env:
|
||||
INSTALL_REF: ${{ needs.release.outputs.version }}
|
||||
with:
|
||||
envs: 'INSTALL_REF'
|
||||
usesh: true
|
||||
mem: 8192
|
||||
disable-cache: true
|
||||
prepare: |
|
||||
pkgman install -y libsdl2 libsdl2_devel git gcc premake5
|
||||
run: |
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
bash projects/scripts/patch_commit_number.sh
|
||||
sh projects/haiku/ecode/build.app.sh --version ${{ env.INSTALL_REF }}
|
||||
- name: Upload Files
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: ${{ needs.release.outputs.version }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
files: |
|
||||
projects/haiku/ecode/ecode-haiku-${{ env.INSTALL_REF }}-x86_64.tar.gz
|
||||
|
||||
build_windows_msvc:
|
||||
name: Windows x86_64 MSVC Nightly
|
||||
|
||||
@@ -853,6 +853,8 @@ class EE_API TextDocument {
|
||||
|
||||
void notifyLineChanged( const Int64& lineIndex );
|
||||
|
||||
void notifyLinesChanged( const Int64& firstLine, const Int64& lastLine );
|
||||
|
||||
void notifyUndoRedo( const UndoRedo& eventType );
|
||||
|
||||
void notifyDirtyOnFileSystem();
|
||||
|
||||
@@ -17,6 +17,23 @@ class EE_API TextDocumentLine {
|
||||
updateState();
|
||||
}
|
||||
|
||||
TextDocumentLine( String&& text, std::shared_ptr<Mutex> docMutex ) :
|
||||
mText( std::move( text ) ), mDocMutex( std::move( docMutex ) ) {
|
||||
updateState();
|
||||
}
|
||||
|
||||
TextDocumentLine( const TextDocumentLine& ) = default;
|
||||
|
||||
TextDocumentLine( TextDocumentLine&& other ) noexcept : mDocMutex( other.mDocMutex ) {
|
||||
ConditionalLock lock( mDocMutex != nullptr, mDocMutex.get() );
|
||||
mText = std::move( other.mText );
|
||||
mHash = other.mHash;
|
||||
mFlags = other.mFlags;
|
||||
other.mDocMutex.reset();
|
||||
}
|
||||
|
||||
TextDocumentLine& operator=( const TextDocumentLine& ) = default;
|
||||
|
||||
~TextDocumentLine() {
|
||||
if ( mDocMutex ) {
|
||||
// Wait for any readers to finish before destruction
|
||||
|
||||
@@ -23,6 +23,12 @@ class EE_API InputMethod {
|
||||
Int32 length{ 0 };
|
||||
};
|
||||
|
||||
/**
|
||||
* Queues the text-input candidate location. Backend updates are coalesced globally and limited
|
||||
* to one every 50 ms by default. Set EEPP_IME_LOCATION_UPDATE_INTERVAL_MS to a non-negative
|
||||
* integer to override the interval (values are capped at 65535 ms); 0 applies the latest
|
||||
* location once per rendered frame.
|
||||
*/
|
||||
void setLocation( Rect rect );
|
||||
|
||||
bool isEditing() const;
|
||||
@@ -51,11 +57,15 @@ class EE_API InputMethod {
|
||||
EE::Window::Window* mWindow{ nullptr };
|
||||
InputMethod::State mState;
|
||||
bool mEditing{ false };
|
||||
bool mLocationDirty{ false };
|
||||
Uint16 mLocationUpdateCooldown{ 0 };
|
||||
Rect mLastLocation;
|
||||
Uint32 mLastCb{ 0 };
|
||||
std::map<Uint32, TextEditingCb> mEditingCbs;
|
||||
|
||||
void sendTextEditing( const String& txt, Int32 start, Int32 length );
|
||||
|
||||
void updateLocation();
|
||||
};
|
||||
|
||||
}} // namespace EE::Window
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <eepp/ui/doc/textdocument.hpp>
|
||||
#include <eepp/window/engine.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <set>
|
||||
|
||||
using namespace std::literals;
|
||||
@@ -1570,30 +1572,47 @@ TextPosition TextDocument::insert( const size_t& cursorIdx, TextPosition positio
|
||||
position = sanitizePosition( position );
|
||||
size_t lineCount = linesCount();
|
||||
Int64 linesAdd = 0;
|
||||
bool multiline = text.find( '\n' ) != String::InvalidPos;
|
||||
|
||||
{
|
||||
Lock l( mLinesMutex );
|
||||
if ( text.find( '\n' ) == String::InvalidPos ) {
|
||||
if ( !multiline ) {
|
||||
mLines[position.line()].insert( position.column(), text );
|
||||
notifyLineChanged( position.line() );
|
||||
} else {
|
||||
String before = mLines[position.line()].substr( 0, position.column() );
|
||||
String after = mLines[position.line()].substr( position.column() );
|
||||
std::vector<String> lines = text.split( '\n', true );
|
||||
linesAdd = eemax<Int64>( 0, static_cast<Int64>( lines.size() ) - 1 );
|
||||
for ( auto i = 0; i < linesAdd; i++ )
|
||||
lines[i] = lines[i] + "\n";
|
||||
lines[0] = before + lines[0];
|
||||
lines[lines.size() - 1] = lines[lines.size() - 1] + after;
|
||||
{
|
||||
Lock l2( *mDocumentMutex );
|
||||
String before = mLines[position.line()].substr( 0, position.column() );
|
||||
String after = mLines[position.line()].substr( position.column() );
|
||||
String::View textView = text.view();
|
||||
linesAdd =
|
||||
static_cast<Int64>( std::count( textView.begin(), textView.end(), '\n' ) );
|
||||
// Build the new document lines once and insert the complete range. Inserting each
|
||||
// line separately repeatedly grows the vector and shifts its trailing elements.
|
||||
SmallVector<TextDocumentLine, 8> lines;
|
||||
lines.reserve( static_cast<size_t>( linesAdd ) );
|
||||
|
||||
mLines[position.line()] = TextDocumentLine( lines[0], mDocumentMutex );
|
||||
notifyLineChanged( position.line() );
|
||||
size_t lineStart = 0;
|
||||
for ( Int64 i = 0; i <= linesAdd; ++i ) {
|
||||
size_t newLine = textView.find( '\n', lineStart );
|
||||
size_t lineEnd = newLine == String::InvalidPos ? textView.size() : newLine + 1;
|
||||
String line( textView.substr( lineStart, lineEnd - lineStart ) );
|
||||
if ( i == 0 )
|
||||
line.insert( 0, before );
|
||||
if ( i == linesAdd )
|
||||
line.append( after );
|
||||
if ( i == 0 )
|
||||
mLines[position.line()].setText( std::move( line ) );
|
||||
else
|
||||
lines.emplace_back( std::move( line ), mDocumentMutex );
|
||||
lineStart = lineEnd;
|
||||
}
|
||||
|
||||
for ( Int64 i = 1; i < (Int64)lines.size(); i++ ) {
|
||||
mLines.insert( mLines.begin() + position.line() + i,
|
||||
TextDocumentLine( lines[i], mDocumentMutex ) );
|
||||
notifyLineChanged( position.line() + i );
|
||||
mLines.insert( mLines.begin() + position.line() + 1,
|
||||
std::make_move_iterator( lines.begin() ),
|
||||
std::make_move_iterator( lines.end() ) );
|
||||
}
|
||||
notifyLinesChanged( position.line(), position.line() + linesAdd );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4398,6 +4417,14 @@ void TextDocument::notifyLineChanged( const Int64& lineIndex ) {
|
||||
}
|
||||
}
|
||||
|
||||
void TextDocument::notifyLinesChanged( const Int64& firstLine, const Int64& lastLine ) {
|
||||
Lock l( mClientsMutex );
|
||||
for ( Int64 lineIndex = firstLine; lineIndex <= lastLine; ++lineIndex ) {
|
||||
for ( auto& client : mClients )
|
||||
client->onDocumentLineChanged( lineIndex );
|
||||
}
|
||||
}
|
||||
|
||||
void TextDocument::notifyUndoRedo( const TextDocument::UndoRedo& eventType ) {
|
||||
Lock l( mClientsMutex );
|
||||
for ( auto& client : mClients ) {
|
||||
|
||||
@@ -1156,8 +1156,8 @@ Uint32 UICodeEditor::onTextInput( const TextInputEvent& event ) {
|
||||
}
|
||||
|
||||
void UICodeEditor::updateIMELocation() {
|
||||
if ( mDoc->isLoading() || mDoc->getActiveClient() != this || !Engine::isMainThread() ||
|
||||
mDocView.isFolded( mDoc->getSelection( true ).start().line() ) )
|
||||
if ( !hasFocus() || mDoc->isLoading() || mDoc->getActiveClient() != this ||
|
||||
!Engine::isMainThread() || mDocView.isFolded( mDoc->getSelection( true ).start().line() ) )
|
||||
return;
|
||||
Rectf r( getScreenPosition( mDoc->getSelection( true ).start() ) );
|
||||
getUISceneNode()->getWindow()->getIME().setLocation( r.asInt() );
|
||||
|
||||
@@ -1051,7 +1051,7 @@ Uint32 UIConsole::onTextEditing( const TextEditingEvent& event ) {
|
||||
}
|
||||
|
||||
void UIConsole::updateIMELocation( const Rectf& loc ) {
|
||||
if ( mDoc.getActiveClient() != this )
|
||||
if ( !hasFocus() || mDoc.getActiveClient() != this )
|
||||
return;
|
||||
getUISceneNode()->getWindow()->getIME().setLocation( loc.asInt() );
|
||||
}
|
||||
|
||||
@@ -941,7 +941,7 @@ Uint32 UITextInput::onTextInput( const TextInputEvent& event ) {
|
||||
}
|
||||
|
||||
void UITextInput::updateIMELocation() {
|
||||
if ( mDoc.getActiveClient() != this || !Engine::isMainThread() )
|
||||
if ( !hasFocus() || mDoc.getActiveClient() != this || !Engine::isMainThread() )
|
||||
return;
|
||||
Vector2f cursor( eefloor( mScreenPos.x + mRealAlignOffset.x + mCurPos.x + mPaddingPx.Left ),
|
||||
mScreenPos.y + mRealAlignOffset.y + mCurPos.y + mPaddingPx.Top );
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
#include <eepp/graphics/primitives.hpp>
|
||||
#include <eepp/graphics/text.hpp>
|
||||
#include <eepp/system/sys.hpp>
|
||||
#include <eepp/window/inputmethod.hpp>
|
||||
#include <eepp/window/window.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace EE { namespace Window {
|
||||
|
||||
static constexpr Uint32 DEFAULT_LOCATION_UPDATE_INTERVAL_MS = 50;
|
||||
|
||||
static Uint16 getLocationUpdateInterval() {
|
||||
static Uint16 interval = [] {
|
||||
std::string value = Sys::getEnv( "EEPP_IME_LOCATION_UPDATE_INTERVAL_MS" );
|
||||
Uint32 parsedValue;
|
||||
if ( value.empty() || !String::fromString( parsedValue, value ) )
|
||||
return static_cast<Uint16>( DEFAULT_LOCATION_UPDATE_INTERVAL_MS );
|
||||
return static_cast<Uint16>(
|
||||
std::min<Uint32>( parsedValue, std::numeric_limits<Uint16>::max() ) );
|
||||
}();
|
||||
return interval;
|
||||
}
|
||||
|
||||
InputMethod::InputMethod( EE::Window::Window* window ) : mWindow( window ) {}
|
||||
|
||||
void InputMethod::sendTextEditing( const String& txt, Int32 start, Int32 length ) {
|
||||
@@ -17,11 +35,32 @@ void InputMethod::setLocation( Rect rect ) {
|
||||
rect = PixelDensity::pxToDpI( rect );
|
||||
#endif
|
||||
if ( rect != mLastLocation ) {
|
||||
mWindow->setTextInputRect( rect );
|
||||
mLastLocation = std::move( rect );
|
||||
mLocationDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void InputMethod::updateLocation() {
|
||||
double elapsed = mWindow->getElapsed().asMilliseconds();
|
||||
if ( elapsed >= mLocationUpdateCooldown )
|
||||
mLocationUpdateCooldown = 0;
|
||||
else
|
||||
mLocationUpdateCooldown -= static_cast<Uint16>( elapsed );
|
||||
|
||||
if ( !mLocationDirty )
|
||||
return;
|
||||
|
||||
if ( mLocationUpdateCooldown > 0 )
|
||||
return;
|
||||
|
||||
// SDL3 can synchronously notify the platform IME (including a DBus round-trip on Linux).
|
||||
// Apply only the latest requested location at a bounded rate and keep the backend call on the
|
||||
// main thread, as required by SDL.
|
||||
mLocationDirty = false;
|
||||
mLocationUpdateCooldown = getLocationUpdateInterval();
|
||||
mWindow->setTextInputRect( mLastLocation );
|
||||
}
|
||||
|
||||
bool InputMethod::isEditing() const {
|
||||
return mEditing;
|
||||
}
|
||||
|
||||
@@ -463,6 +463,7 @@ void Window::display( bool clear ) {
|
||||
GlobalBatchRenderer::instance()->draw();
|
||||
if ( TextureFactory* textureFactory = TextureFactory::existsSingleton() )
|
||||
textureFactory->collectReleasedTextures();
|
||||
mIME.updateLocation();
|
||||
|
||||
swapBuffers();
|
||||
|
||||
|
||||
@@ -127,6 +127,27 @@ UTEST( TextDocument, insertIntoExistingLines ) {
|
||||
EXPECT_STRINGEQ( "Yond\n", doc.line( 3 ).getText() );
|
||||
}
|
||||
|
||||
UTEST( TextDocument, insertLargeMultilineBlock ) {
|
||||
constexpr size_t insertedLineCount = 8192;
|
||||
String text;
|
||||
text.reserve( insertedLineCount * 4 + 4 );
|
||||
for ( size_t i = 0; i < insertedLineCount; ++i )
|
||||
text.append( "row\n" );
|
||||
text.append( "last" );
|
||||
|
||||
TextDocument doc;
|
||||
doc.insert( 0, { 0, 0 }, "head\nmiddle\ntail" );
|
||||
TextPosition cursor = doc.insert( 0, { 1, 3 }, text );
|
||||
|
||||
EXPECT_EQ( insertedLineCount + 1, static_cast<size_t>( cursor.line() ) );
|
||||
EXPECT_EQ( 4, cursor.column() );
|
||||
EXPECT_EQ( insertedLineCount + 3, doc.linesCount() );
|
||||
EXPECT_STRINGEQ( "midrow\n", doc.line( 1 ).getText() );
|
||||
EXPECT_STRINGEQ( "row\n", doc.line( insertedLineCount ).getText() );
|
||||
EXPECT_STRINGEQ( "lastdle\n", doc.line( insertedLineCount + 1 ).getText() );
|
||||
EXPECT_STRINGEQ( "tail\n", doc.line( insertedLineCount + 2 ).getText() );
|
||||
}
|
||||
|
||||
UTEST( TextDocument, insertEmptyTextDoesNothing ) {
|
||||
TextDocument doc;
|
||||
doc.insert( 0, { 0, 0 }, "content" );
|
||||
|
||||
Reference in New Issue
Block a user