mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-08-11 17:17:42 +03:00
This should fix two crashes:
* A crash during debugging that happens due to invalid memory access (UIAbstractView + VariablesModel changes). * A crash on ChatUI when closing a chat that was about to receive a server response.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#ifndef EE_UI_UIABSTRACTVIEW_HPP
|
||||
#define EE_UI_UIABSTRACTVIEW_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <eepp/ui/keyboardshortcut.hpp>
|
||||
#include <eepp/ui/models/model.hpp>
|
||||
#include <eepp/ui/models/modeleditingdelegate.hpp>
|
||||
@@ -150,6 +151,7 @@ class EE_API UIAbstractView : public UIScrollableWidget {
|
||||
std::vector<KeyBindings::Shortcut> mEditShortcuts{ { KEY_F2 } };
|
||||
SelectionType mSelectionType{ SelectionType::Row };
|
||||
SelectionKind mSelectionKind{ SelectionKind::Single };
|
||||
std::atomic<unsigned> mPendingUpdateFlags{ 0 };
|
||||
|
||||
virtual void editingWidgetDidChange( const ModelIndex& ) {}
|
||||
};
|
||||
|
||||
@@ -118,11 +118,12 @@ size_t UIAbstractTableView::getItemCount() const {
|
||||
}
|
||||
|
||||
void UIAbstractTableView::onModelUpdate( unsigned flags ) {
|
||||
mPendingUpdateFlags.fetch_or( flags );
|
||||
if ( !Engine::instance()->isMainThread() ) {
|
||||
removeActionsByTag( onModelUpdateTag );
|
||||
runOnMainThread(
|
||||
[this, flags] {
|
||||
modelUpdate( flags );
|
||||
[this] {
|
||||
modelUpdate( mPendingUpdateFlags.exchange( 0 ) );
|
||||
createOrUpdateColumns( true );
|
||||
},
|
||||
Time::Zero, onModelUpdateTag );
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
namespace EE { namespace UI { namespace Abstract {
|
||||
|
||||
static constexpr String::HashType OnModelUpdateTag = String::hash( "onModelUpdate" );
|
||||
|
||||
UIAbstractView::UIAbstractView( const std::string& tag ) :
|
||||
UIScrollableWidget( tag ), mSelection( this ) {}
|
||||
|
||||
@@ -126,7 +128,8 @@ void UIAbstractView::setModel( const std::shared_ptr<Model>& model ) {
|
||||
}
|
||||
|
||||
void UIAbstractView::modelUpdate( unsigned flags ) {
|
||||
if ( !getModel() || ( flags & Model::InvalidateAllIndexes ) ) {
|
||||
mPendingUpdateFlags = 0;
|
||||
if ( !getModel() || ( flags & Model::UpdateFlag::InvalidateAllIndexes ) ) {
|
||||
getSelection().clear();
|
||||
} else {
|
||||
getSelection().removeAllMatching(
|
||||
@@ -135,12 +138,13 @@ void UIAbstractView::modelUpdate( unsigned flags ) {
|
||||
}
|
||||
|
||||
void UIAbstractView::onModelUpdate( unsigned flags ) {
|
||||
mPendingUpdateFlags.fetch_or( flags );
|
||||
if ( !Engine::instance()->isMainThread() ) {
|
||||
static constexpr String::HashType tag = String::hash( "onModelUpdate" );
|
||||
removeActionsByTag( tag );
|
||||
runOnMainThread( [this, flags] { modelUpdate( flags ); }, Time::Zero, tag );
|
||||
removeActionsByTag( OnModelUpdateTag );
|
||||
runOnMainThread( [this] { modelUpdate( mPendingUpdateFlags.exchange( 0 ) ); }, Time::Zero,
|
||||
OnModelUpdateTag );
|
||||
} else {
|
||||
modelUpdate( flags );
|
||||
modelUpdate( mPendingUpdateFlags.exchange( 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -595,6 +595,16 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
|
||||
}
|
||||
|
||||
LLMChatUI::~LLMChatUI() {
|
||||
if ( mRequest ) {
|
||||
mRequest->cancelCb = nullptr;
|
||||
mRequest->doneCb = nullptr;
|
||||
mRequest->streamedResponseCb = nullptr;
|
||||
}
|
||||
if ( mSummaryRequest ) {
|
||||
mSummaryRequest->cancelCb = nullptr;
|
||||
mSummaryRequest->doneCb = nullptr;
|
||||
mSummaryRequest->streamedResponseCb = nullptr;
|
||||
}
|
||||
if ( getPlugin() ) {
|
||||
AIAssistantPlugin::AIAssistantConfig config;
|
||||
config.partition = getSplitter()->getSplitPartition();
|
||||
@@ -1168,28 +1178,27 @@ void LLMChatUI::generateChatName( bool isRenaming ) {
|
||||
mSummaryRequest->doneCb = [this, isRenaming]( const LLMChatCompletionRequest& req,
|
||||
Http::Response& response ) {
|
||||
auto status = response.getStatus();
|
||||
String oldSummary = std::move( mSummary );
|
||||
runOnMainThread( [this, isRenaming, status, responseText = req.getResponse()] {
|
||||
String oldSummary = std::move( mSummary );
|
||||
|
||||
if ( status == Http::Response::Ok ) {
|
||||
mSummary = String::trim( req.getResponse() );
|
||||
String::trimInPlace( mSummary, '\n' );
|
||||
String::trimInPlace( mSummary, ' ' );
|
||||
String::trimInPlace( mSummary, '"' );
|
||||
} else {
|
||||
// TODO: Implement generating a summary based on the user prompt (take the
|
||||
// first few words)
|
||||
mSummary = i18n( "untitled_conversation", "Untitled Conversation" );
|
||||
}
|
||||
if ( status == Http::Response::Ok ) {
|
||||
mSummary = String::trim( responseText );
|
||||
String::trimInPlace( mSummary, '\n' );
|
||||
String::trimInPlace( mSummary, ' ' );
|
||||
String::trimInPlace( mSummary, '"' );
|
||||
} else {
|
||||
// TODO: Implement generating a summary based on the user prompt (take the
|
||||
// first few words)
|
||||
mSummary = i18n( "untitled_conversation", "Untitled Conversation" );
|
||||
}
|
||||
|
||||
if ( isRenaming ) {
|
||||
String newSummary = std::move( mSummary );
|
||||
mSummary = std::move( oldSummary );
|
||||
runOnMainThread(
|
||||
[this, newSummary = std::move( newSummary )] { renameChat( newSummary ); } );
|
||||
} else
|
||||
saveChat();
|
||||
if ( isRenaming ) {
|
||||
String newSummary = std::move( mSummary );
|
||||
mSummary = std::move( oldSummary );
|
||||
renameChat( newSummary );
|
||||
} else
|
||||
saveChat();
|
||||
|
||||
runOnMainThread( [this] {
|
||||
updateTabTitle();
|
||||
mSummaryRequest.reset();
|
||||
} );
|
||||
@@ -1258,15 +1267,17 @@ void LLMChatUI::doRequest() {
|
||||
};
|
||||
|
||||
mRequest->cancelCb = [this, thinking, thinkingID, editor]( const LLMChatCompletionRequest& ) {
|
||||
thinking->removeActionsByTag( thinkingID );
|
||||
thinking->setVisible( false );
|
||||
mChatStop->setVisible( false )->setEnabled( false );
|
||||
mChatRun->setVisible( true )->setEnabled( true );
|
||||
toggleEnableChats( true );
|
||||
editor->setEnabled( true );
|
||||
if ( editor->hasFocus() )
|
||||
mChatInput->setFocus();
|
||||
removeLastChat();
|
||||
runOnMainThread( [this, thinking, thinkingID, editor] {
|
||||
thinking->removeActionsByTag( thinkingID );
|
||||
thinking->setVisible( false );
|
||||
mChatStop->setVisible( false )->setEnabled( false );
|
||||
mChatRun->setVisible( true )->setEnabled( true );
|
||||
toggleEnableChats( true );
|
||||
editor->setEnabled( true );
|
||||
if ( editor->hasFocus() )
|
||||
mChatInput->setFocus();
|
||||
removeLastChat();
|
||||
} );
|
||||
};
|
||||
|
||||
mRequest->doneCb =
|
||||
|
||||
@@ -120,6 +120,9 @@ LLMChatCompletionRequest::LLMChatCompletionRequest( const std::string& uri, cons
|
||||
}
|
||||
|
||||
LLMChatCompletionRequest::~LLMChatCompletionRequest() {
|
||||
cancelCb = nullptr;
|
||||
doneCb = nullptr;
|
||||
streamedResponseCb = nullptr;
|
||||
cancel();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../debuggerclient.hpp"
|
||||
#include <eepp/ui/uiscenenode.hpp>
|
||||
#include <eepp/ui/uitreeview.hpp>
|
||||
#include <eepp/window/engine.hpp>
|
||||
|
||||
namespace ecode {
|
||||
|
||||
@@ -73,6 +74,7 @@ VariablesModel::VariablesModel( ModelVariableNode::NodePtr rootNode, UISceneNode
|
||||
mRootNode( rootNode ), mSceneNode( sceneNode ) {}
|
||||
|
||||
ModelIndex VariablesModel::index( int row, int column, const ModelIndex& parent ) const {
|
||||
checkQueuedClear( parent );
|
||||
if ( !mRootNode )
|
||||
return ModelIndex();
|
||||
|
||||
@@ -115,6 +117,7 @@ ModelIndex VariablesModel::parentIndex( const ModelIndex& index ) const {
|
||||
}
|
||||
|
||||
size_t VariablesModel::rowCount( const ModelIndex& index ) const {
|
||||
checkQueuedClear( index );
|
||||
ModelVariableNode* parentNode =
|
||||
index.isValid() ? static_cast<ModelVariableNode*>( index.internalData() ) : mRootNode.get();
|
||||
|
||||
@@ -122,6 +125,7 @@ size_t VariablesModel::rowCount( const ModelIndex& index ) const {
|
||||
}
|
||||
|
||||
bool VariablesModel::hasChildren( const ModelIndex& index ) const {
|
||||
checkQueuedClear( index );
|
||||
if ( !index.isValid() )
|
||||
return !mRootNode->children.empty();
|
||||
ModelVariableNode* node = static_cast<ModelVariableNode*>( index.internalData() );
|
||||
@@ -165,9 +169,21 @@ Variant VariablesModel::data( const ModelIndex& index, ModelRole role ) const {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
void VariablesModel::checkQueuedClear( const ModelIndex& index ) const {
|
||||
if ( !index.isValid() && mQueuedClear && Engine::instance()->isMainThread() ) {
|
||||
mChildMap.clear();
|
||||
mQueuedClear = false;
|
||||
}
|
||||
}
|
||||
|
||||
void VariablesModel::invalidate( unsigned int flags ) {
|
||||
if ( flags & Model::UpdateFlag::InvalidateAllIndexes ) {
|
||||
mChildMap.clear();
|
||||
if ( Engine::instance()->isMainThread() ) {
|
||||
mChildMap.clear();
|
||||
mQueuedClear = false;
|
||||
} else {
|
||||
mQueuedClear = true;
|
||||
}
|
||||
}
|
||||
Model::invalidate( flags );
|
||||
}
|
||||
@@ -246,7 +262,7 @@ void VariablesHolder::upsertRootChild( Variable&& var ) {
|
||||
auto newChild = std::make_shared<ModelVariableNode>( std::move( var ), mRootNode );
|
||||
mNodeMap[newChild->var.variablesReference] = newChild;
|
||||
mRootNode->children[i] = std::move( newChild );
|
||||
mModel->invalidate( Model::UpdateFlag::DontInvalidateIndexes );
|
||||
mModel->invalidate( Model::UpdateFlag::InvalidateAllIndexes );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,9 @@ class VariablesModel : public Model {
|
||||
ModelVariableNode::NodePtr mRootNode;
|
||||
mutable std::unordered_map<ModelVariableNode*, ModelVariableNode::NodePtr> mChildMap;
|
||||
UISceneNode* mSceneNode;
|
||||
mutable bool mQueuedClear{ false };
|
||||
|
||||
void checkQueuedClear( const ModelIndex& index ) const;
|
||||
};
|
||||
|
||||
class VariablesHolder {
|
||||
|
||||
Reference in New Issue
Block a user