From 4e6cb747741bff11306aed2f3c5d45e6e7e25ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 13 Feb 2023 02:05:08 -0300 Subject: [PATCH] ecode: Fixes for SpartanJ/ecode#45. Minor performance optimizations. Minor bug fixes. --- include/eepp/ui/uiconsole.hpp | 2 + src/eepp/system/process.cpp | 20 +++--- src/eepp/ui/uiconsole.cpp | 4 ++ src/eepp/window/backend/SDL2/inputsdl2.cpp | 2 +- src/tools/ecode/ecode.cpp | 18 +++--- src/tools/ecode/ecode.hpp | 4 +- .../ecode/plugins/lsp/lspclientplugin.cpp | 10 +-- .../ecode/plugins/lsp/lspclientplugin.hpp | 2 +- .../ecode/plugins/lsp/lspclientserver.cpp | 63 ++++++++++--------- src/tools/ecode/plugins/pluginmanager.cpp | 4 +- 10 files changed, 70 insertions(+), 59 deletions(-) diff --git a/include/eepp/ui/uiconsole.hpp b/include/eepp/ui/uiconsole.hpp index 379c5c674..3f91721e1 100644 --- a/include/eepp/ui/uiconsole.hpp +++ b/include/eepp/ui/uiconsole.hpp @@ -78,6 +78,8 @@ class EE_API UIConsole : public UIWidget, void addCommand( const std::string& command, const ConsoleCallback& cb ); + void setCommand( const std::string& command, const ConsoleCallback& cb ); + const Uint32& getMaxLogLines() const; void setMaxLogLines( const Uint32& maxLogLines ); diff --git a/src/eepp/system/process.cpp b/src/eepp/system/process.cpp index 088faebb8..e8bf9a64d 100644 --- a/src/eepp/system/process.cpp +++ b/src/eepp/system/process.cpp @@ -37,12 +37,12 @@ Process::~Process() { mShuttingDown = true; if ( mProcess ) destroy(); + if ( mProcess && isAlive() ) + kill(); if ( mStdOutThread.joinable() ) mStdOutThread.join(); if ( mStdErrThread.joinable() ) mStdErrThread.join(); - if ( mProcess && isAlive() ) - kill(); eeFree( mProcess ); } @@ -199,13 +199,13 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) { SUBPROCESS_PTR_CAST( void*, _get_osfhandle( _fileno( PROCESS_PTR->stderr_file ) ) ); if ( stdOutFd ) { mStdOutThread = std::thread( [this, stdOutFd]() { - DWORD n; + unsigned n; std::string buffer; buffer.resize( mBufferSize ); while ( !mShuttingDown ) { - BOOL bSuccess = ReadFile( stdOutFd, static_cast( &buffer[0] ), - static_cast( mBufferSize ), &n, nullptr ); - if ( !bSuccess || n == 0 ) + n = subprocess_read_stdout( PROCESS_PTR, static_cast( &buffer[0] ), + mBufferSize ); + if ( n == 0 ) break; if ( n < static_cast( mBufferSize - 1 ) ) buffer[n] = '\0'; @@ -216,13 +216,13 @@ void Process::startAsyncRead( ReadFn readStdOut, ReadFn readStdErr ) { } if ( stdErrFd && stdErrFd != stdOutFd ) { mStdErrThread = std::thread( [this, stdErrFd]() { - DWORD n; + unsigned n; std::string buffer; buffer.resize( mBufferSize ); while ( !mShuttingDown ) { - BOOL bSuccess = ReadFile( stdErrFd, static_cast( &buffer[0] ), - static_cast( mBufferSize ), &n, nullptr ); - if ( !bSuccess || n == 0 ) + n = subprocess_read_stderr( PROCESS_PTR, static_cast( &buffer[0] ), + mBufferSize ); + if ( n == 0 ) break; if ( n < static_cast( mBufferSize - 1 ) ) buffer[n] = '\0'; diff --git a/src/eepp/ui/uiconsole.cpp b/src/eepp/ui/uiconsole.cpp index 14975cbde..bb31bcf23 100644 --- a/src/eepp/ui/uiconsole.cpp +++ b/src/eepp/ui/uiconsole.cpp @@ -353,6 +353,10 @@ void UIConsole::addCommand( const std::string& command, const ConsoleCallback& c mCallbacks[command] = cb; } +void UIConsole::setCommand( const std::string& command, const ConsoleCallback& cb ) { + mCallbacks[command] = cb; +} + const Uint32& UIConsole::getMaxLogLines() const { return mMaxLogLines; } diff --git a/src/eepp/window/backend/SDL2/inputsdl2.cpp b/src/eepp/window/backend/SDL2/inputsdl2.cpp index ca983269a..2ad5f7590 100644 --- a/src/eepp/window/backend/SDL2/inputsdl2.cpp +++ b/src/eepp/window/backend/SDL2/inputsdl2.cpp @@ -29,7 +29,7 @@ void InputSDL::update() { mEventsSentId = 0; if ( !mQueuedEvents.empty() ) { - for ( auto prevEvent : mQueuedEvents ) + for ( const auto& prevEvent : mQueuedEvents ) sendEvent( prevEvent ); mQueuedEvents.clear(); } diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 5fd5e9d06..92f3da948 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -46,7 +46,6 @@ bool App::onCloseRequestCallback( EE::Window::Window* ) { saveConfig(); mWindow->close(); } ); - msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { msgBox = nullptr; } ); msgBox->setTitle( String::format( i18n( "close_title", "Close %s?" ).toUtf8().c_str(), mWindowTitle.c_str() ) ); msgBox->center(); @@ -278,8 +277,6 @@ void App::openFontDialog( std::string& fontPath, bool loadingMonoFont ) { msgBox->addEventListener( Event::OnCancel, [fontMono]( const Event* ) { FontManager::instance()->remove( fontMono ); } ); - msgBox->addEventListener( Event::OnClose, - [&]( const Event* ) { msgBox = nullptr; } ); msgBox->setTitle( i18n( "confirm_loading_font", "Font loading confirmation" ) ); msgBox->center(); msgBox->showWhenReady(); @@ -560,7 +557,8 @@ void App::onTextDropped( String text ) { } } -App::App() : mThreadPool( ThreadPool::createShared( eemax( 2, Sys::getCPUCount() ) ) ) {} +App::App( const size_t& jobs ) : + mThreadPool( ThreadPool::createShared( jobs > 0 ? jobs : eemax( 2, Sys::getCPUCount() ) ) ) {} App::~App() { mThreadPool.reset(); @@ -1490,7 +1488,6 @@ void App::closeFolder() { i18n( "confirm_close_folder", "Do you really want to close the folder?\nSome files haven't been saved." ) ); msgBox->addEventListener( Event::OnConfirm, [&]( const Event* ) { closeEditors(); } ); - msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { msgBox = nullptr; } ); msgBox->setTitle( i18n( "close_folder_question", "Close Folder?" ) ); msgBox->center(); msgBox->showWhenReady(); @@ -2301,7 +2298,7 @@ FontTrueType* App::loadFont( const std::string& name, std::string fontPath, void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDensity, const std::string& colorScheme, bool terminal, bool frameBuffer, bool benchmarkMode, - const std::string& css ) { + const std::string& css, const size_t& jobs ) { DisplayManager* displayManager = Engine::instance()->getDisplayManager(); Display* currentDisplay = displayManager->getDisplayIndex( 0 ); mDisplayDPI = currentDisplay->getDPI(); @@ -2928,6 +2925,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe mSplitter->createEditorWithTabWidget( mBaseLayout ); mConsole = UIConsole::NewOpt( mFontMono, true, true, 1024 * 10 ); + mConsole->setCommand( "hide", [&]( const auto& params ) { consoleToggle(); } ); mConsole->setQuakeMode( true ); mConsole->setVisible( false ); @@ -2998,6 +2996,10 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { args::Flag verbose( parser, "verbose", "Print all logs to the standard output.", { 'v', "verbose" } ); args::Flag version( parser, "version", "Prints version information", { 'V', "version" } ); + args::ValueFlag jobs( parser, "jobs", + "Sets the number of background jobs that the application will spawn " + "at the start of the application", + { 'j', "jobs" }, 0 ); try { parser.ParseCLI( Sys::parseArguments( argc, argv ) ); } catch ( const args::Help& ) { @@ -3021,11 +3023,11 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { if ( verbose.Get() ) Log::instance()->setConsoleOutput( true ); - appInstance = eeNew( App, () ); + appInstance = eeNew( App, ( jobs ) ); appInstance->init( logLevel.Get(), filePos ? filePos.Get() : file.Get(), pixelDenstiyConf ? pixelDenstiyConf.Get() : 0.f, prefersColorScheme ? prefersColorScheme.Get() : "", terminal.Get(), fb.Get(), - benchmarkMode.Get(), css.Get() ); + benchmarkMode.Get(), css.Get(), jobs.Get() ); eeSAFE_DELETE( appInstance ); Engine::destroySingleton(); diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index e88e0a193..8d1d6f5c4 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -26,13 +26,13 @@ class SettingsMenu; class App : public UICodeEditorSplitter::Client { public: - App(); + App( const size_t& jobs = 0 ); ~App(); void init( const LogLevel& logLevel, std::string file, const Float& pidelDensity, const std::string& colorScheme, bool terminal, bool frameBuffer, bool benchmarkMode, - const std::string& css ); + const std::string& css, const size_t& jobs ); void createWidgetInspector(); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 0c442f5dc..4fada918e 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -418,14 +418,14 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std if ( obj.contains( "initializationOptions" ) ) lsp.initializationOptions = obj["initializationOptions"]; - auto fp = obj["file_patterns"]; + auto& fp = obj["file_patterns"]; for ( auto& pattern : fp ) lsp.filePatterns.push_back( pattern.get() ); if ( obj.contains( "rootIndicationFileNames" ) ) { lsp.rootIndicationFileNames.clear(); - auto fnms = obj["rootIndicationFileNames"]; + auto& fnms = obj["rootIndicationFileNames"]; for ( auto& fn : fnms ) lsp.rootIndicationFileNames.push_back( fn ); } @@ -562,13 +562,13 @@ void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { return; Lock l( mDocMutex ); TextDocument* doc = mEditorDocs[editor]; - auto cbs = mEditors[editor]; + auto& cbs = mEditors[editor]; for ( auto listener : cbs ) editor->removeEventListener( listener ); mEditors.erase( editor ); mEditorsTags.erase( editor ); mEditorDocs.erase( editor ); - for ( auto editor : mEditorDocs ) + for ( auto& editor : mEditorDocs ) if ( editor.second == doc ) return; mDocs.erase( doc ); @@ -591,7 +591,7 @@ bool LSPClientPlugin::onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* me KeyBindings::keybindFormat( mKeyBindings[txtKey] ) ) ->setId( txtKey ); }; - auto cap = server->getCapabilities(); + auto& cap = server->getCapabilities(); addFn( "lsp-symbol-info", "Symbol Info" ); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp index 1ed3ef0f4..4adb44cd4 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.hpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.hpp @@ -88,7 +88,7 @@ class LSPClientPlugin : public UICodeEditorPlugin { bool mSymbolInfoShowing{ false }; std::map mKeyBindings; /* cmd, shortcut */ std::map> mDelayedDocs; - Uint32 mHoverWaitCb; + Uint32 mHoverWaitCb{ 0 }; LSPHover mCurrentHover; Time mHoverDelay{ Seconds( 1.f ) }; Uint32 mOldTextStyle{ 0 }; diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index 9f88cf946..338e5bd5c 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -171,7 +171,7 @@ static LSPLocation parseLocation( const json& loc ) { static LSPLocation parseLocationLink( const json& loc ) { auto uri = URI( loc[MEMBER_TARGET_URI].get() ); - auto vrange = loc[MEMBER_TARGET_SELECTION_RANGE]; + json vrange = loc[MEMBER_TARGET_SELECTION_RANGE]; if ( vrange.is_null() ) vrange = loc[MEMBER_TARGET_RANGE]; auto range = parseRange( vrange ); @@ -230,7 +230,7 @@ static json textDocumentPositionsParams( const URI& document, static void fromJson( std::vector& trigger, const json& json ) { if ( !json.empty() ) { - const auto triggersArray = json; + const auto& triggersArray = json; for ( const auto& t : triggersArray ) { auto st = t.get(); if ( st.length() ) @@ -251,7 +251,7 @@ static void fromJson( LSPCompletionOptions& options, const json& json ) { static void fromJson( LSPSignatureHelpOptions& options, const json& json ) { if ( !json.empty() && json.is_object() ) { - auto ob = json; + auto& ob = json; options.provider = true; fromJson( options.triggerCharacters, ob["triggerCharacters"] ); } @@ -259,9 +259,10 @@ static void fromJson( LSPSignatureHelpOptions& options, const json& json ) { static void fromJson( LSPDocumentOnTypeFormattingOptions& options, const json& json ) { if ( !json.empty() && json.is_object() ) { - auto ob = json; + auto& ob = json; options.provider = true; - fromJson( options.triggerCharacters, ob["moreTriggerCharacter"] ); + if ( ob.contains( "moreTriggerCharacter" ) ) + fromJson( options.triggerCharacters, ob["moreTriggerCharacter"] ); auto trigger = ob["firstTriggerCharacter"].get(); if ( trigger.size() ) options.triggerCharacters.push_back( trigger.at( 0 ) ); @@ -270,7 +271,7 @@ static void fromJson( LSPDocumentOnTypeFormattingOptions& options, const json& j static void fromJson( LSPWorkspaceFoldersServerCapabilities& options, const json& json ) { if ( json.is_object() ) { - auto ob = json; + auto& ob = json; options.supported = ob.value( "supported", false ); if ( ob["changeNotifications"].is_boolean() ) { options.changeNotifications = ob["changeNotifications"].get(); @@ -290,11 +291,11 @@ static void fromJson( LSPServerCapabilities& caps, const json& json ) { ( value[valueName].is_boolean() || value[valueName].is_object() ); }; - auto sync = json["textDocumentSync"]; + auto& sync = json["textDocumentSync"]; caps.textDocumentSync.change = static_cast( ( sync.is_object() ? sync["change"].get() : sync.get() ) ); if ( sync.is_object() && sync.contains( "save" ) ) { - auto save = sync["save"]; + auto& save = sync["save"]; if ( save.is_boolean() ) { caps.textDocumentSync.save.includeText = save.get(); } else if ( save.is_object() && save.contains( "includeText" ) ) { @@ -324,12 +325,12 @@ static void fromJson( LSPServerCapabilities& caps, const json& json ) { caps.renameProvider = toBoolOrObject( json, "renameProvider" ); if ( json.contains( "codeActionProvider" ) && json["codeActionProvider"].contains( "resolveProvider" ) ) { - auto codeActionProvider = json["codeActionProvider"]; - caps.codeActionProvider = json["codeActionProvider"]["resolveProvider"].get(); + auto& codeActionProvider = json["codeActionProvider"]; + caps.codeActionProvider = codeActionProvider["resolveProvider"].get(); } // fromJson( caps.semanticTokenProvider, json["semanticTokensProvider"] ); if ( json.contains( "workspace" ) ) { - auto workspace = json["workspace"]; + auto& workspace = json["workspace"]; fromJson( caps.workspaceFolders, workspace["workspaceFolders"] ); } caps.selectionRangeProvider = toBoolOrObject( json, "selectionRangeProvider" ); @@ -393,8 +394,8 @@ static std::vector parseWorkspaceSymbols( const json& res res.cbegin(), res.cend(), std::back_inserter( symbols ), []( const json& symbol ) { LSPSymbolInformation symInfo; - const auto location = symbol.at( MEMBER_LOCATION ); - const auto mrange = symbol.contains( MEMBER_RANGE ) ? symbol.at( MEMBER_RANGE ) + const auto& location = symbol.at( MEMBER_LOCATION ); + const auto& mrange = symbol.contains( MEMBER_RANGE ) ? symbol.at( MEMBER_RANGE ) : location.at( MEMBER_RANGE ); auto containerName = symbol.value( "containerName", "" ); @@ -443,7 +444,7 @@ static std::vector parseTextEditArray( const json& result ) { static void fromJson( LSPVersionedTextDocumentIdentifier& id, const json& json ) { if ( json.is_object() ) { - auto ob = json; + auto& ob = json; id.uri = URI( ob.at( MEMBER_URI ).get() ); id.version = ob.contains( MEMBER_VERSION ) ? ob.at( MEMBER_VERSION ).get() : -1; } @@ -451,7 +452,7 @@ static void fromJson( LSPVersionedTextDocumentIdentifier& id, const json& json ) static LSPTextDocumentEdit parseTextDocumentEdit( const json& result ) { LSPTextDocumentEdit ret; - auto ob = result; + auto& ob = result; fromJson( ret.textDocument, ob.at( "textDocument" ) ); ret.edits = parseTextEditArray( ob.at( "edits" ) ); return ret; @@ -460,16 +461,16 @@ static LSPTextDocumentEdit parseTextDocumentEdit( const json& result ) { static LSPWorkspaceEdit parseWorkSpaceEdit( const json& result ) { LSPWorkspaceEdit ret; if ( result.contains( "changes" ) ) { - auto changes = result.at( "changes" ); + auto& changes = result.at( "changes" ); for ( auto it = changes.begin(); it != changes.end(); ++it ) { ret.changes.insert( std::pair>( URI( it.key() ), parseTextEditArray( it.value() ) ) ); } } if ( result.contains( "documentChanges" ) ) { - auto documentChanges = result.at( "documentChanges" ); + auto& documentChanges = result.at( "documentChanges" ); // resourceOperations not supported for now - for ( auto edit : documentChanges ) { + for ( auto& edit : documentChanges ) { ret.documentChanges.push_back( parseTextDocumentEdit( edit ) ); } } @@ -479,7 +480,7 @@ static LSPWorkspaceEdit parseWorkSpaceEdit( const json& result ) { static LSPCommand parseCommand( const json& result ) { auto title = result.at( MEMBER_TITLE ).get(); auto command = result.at( MEMBER_COMMAND ).get(); - auto args = result.at( MEMBER_ARGUMENTS ); + auto& args = result.at( MEMBER_ARGUMENTS ); return { title, command, args }; } @@ -523,12 +524,12 @@ static std::vector parseCodeAction( const json& result ) { // CodeAction auto title = action.at( MEMBER_TITLE ).get(); auto kind = action.value( MEMBER_KIND, "" ); - auto command = action.contains( MEMBER_COMMAND ) + auto& command = action.contains( MEMBER_COMMAND ) ? parseCommand( action.at( MEMBER_COMMAND ) ) : LSPCommand{}; - auto edit = action.at( MEMBER_EDIT ) ? parseWorkSpaceEdit( action.at( MEMBER_EDIT ) ) + auto& edit = action.at( MEMBER_EDIT ) ? parseWorkSpaceEdit( action.at( MEMBER_EDIT ) ) : LSPWorkspaceEdit{}; - auto diagnostics = action.contains( MEMBER_DIAGNOSTICS ) + auto& diagnostics = action.contains( MEMBER_DIAGNOSTICS ) ? parseDiagnostics( action.at( MEMBER_DIAGNOSTICS ) ) : std::vector{}; LSPCodeAction action = { title, kind, diagnostics, edit, command }; @@ -550,7 +551,7 @@ static std::vector parseDiagnosticsCodeAction( const j auto title = action.at( MEMBER_TITLE ).get(); auto kind = action.value( MEMBER_KIND, "" ); auto isPreferred = action.value( "isPreferred", false ); - auto edit = action.contains( MEMBER_EDIT ) + auto& edit = action.contains( MEMBER_EDIT ) ? parseWorkSpaceEdit( action.at( MEMBER_EDIT ) ) : LSPWorkspaceEdit{}; LSPDiagnosticsCodeAction action = { title, kind, isPreferred, edit }; @@ -665,7 +666,7 @@ static std::vector parseDiagnosticsArr( const json& result ) { for ( const auto& diag : result ) { auto range = parseRange( diag[MEMBER_RANGE] ); auto severity = static_cast( diag["severity"].get() ); - auto code = diag.contains( "code" ) ? ( diag["code"].is_number_integer() + auto& code = diag.contains( "code" ) ? ( diag["code"].is_number_integer() ? String::toString( diag["code"].get() ) : diag.at( "code" ).get() ) : ""; @@ -745,7 +746,7 @@ static LSPSignatureInformation parseSignatureInformation( const json& json ) { info.documentation = parseMarkupContent( json.at( MEMBER_DOCUMENTATION ) ); const auto& params = json.at( "parameters" ); for ( const auto& par : params ) { - auto label = par.at( MEMBER_LABEL ); + auto& label = par.at( MEMBER_LABEL ); int begin = -1, end = -1; if ( label.is_array() ) { auto& range = label; @@ -948,7 +949,7 @@ bool LSPClientServer::start() { cmd += mLSP.commandParameters; } bool ret = - mProcess.create( cmd, Process::getDefaultOptions(), {}, mRootPath ); + mProcess.create( cmd, Process::getDefaultOptions() | Process::EnableAsync, {}, mRootPath ); if ( ret && mProcess.isAlive() ) { mProcess.startAsyncRead( [this]( const char* bytes, size_t n ) { readStdOut( bytes, n ); }, @@ -1013,7 +1014,7 @@ LSPClientServer::LSPRequestHandle LSPClientServer::write( const json& msg, if ( !mProcess.isAlive() ) return ret; - auto ob = msg; + json ob = msg; ob["jsonrpc"] = "2.0"; // notification == no handler @@ -1192,9 +1193,9 @@ LSPClientServer::workspaceSymbol( const std::string& querySymbol, } ); } -void fromJson( LSPWorkDoneProgressValue& value, const json& json ) { - if ( !json.empty() ) { - auto ob = json; +void fromJson( LSPWorkDoneProgressValue& value, const json& data ) { + if ( !data.empty() ) { + json ob = data; auto kind = ob["kind"].get(); if ( kind == "begin" ) { value.kind = LSPWorkDoneProgressKind::Begin; @@ -1335,7 +1336,7 @@ void LSPClientServer::readStdOut( const char* bytes, size_t n ) { while ( !mProcess.isShootingDown() ) { auto index = buffer.find( CONTENT_LENGTH_HEADER ); if ( index == std::string::npos ) { - if ( buffer.size() > ( 1 << 20 ) ) + if ( buffer.size() > ( (Uint64)1 << 20 ) ) buffer.clear(); break; } diff --git a/src/tools/ecode/plugins/pluginmanager.cpp b/src/tools/ecode/plugins/pluginmanager.cpp index ed24d9745..6e81dccbc 100644 --- a/src/tools/ecode/plugins/pluginmanager.cpp +++ b/src/tools/ecode/plugins/pluginmanager.cpp @@ -376,7 +376,9 @@ UIWindow* UIPluginManager::New( UISceneNode* sceneNode, PluginManager* manager, } }; tv->addEventListener( Event::OnClose, [&, manager, tv]( const Event* ) { - for ( auto& cb : tv->readyCbs ) { + if ( tv->readyCbs.empty() ) + return; + for ( const auto& cb : tv->readyCbs ) { auto* plugin = manager->get( cb.first ); if ( plugin ) plugin->removeReadyCallback( cb.second );