-
Notifications
You must be signed in to change notification settings - Fork 635
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve UI responsiveness when doing node search #15773
Changes from 3 commits
ba85860
37ba109
f731c5f
f58edcb
c7b81bf
8fca368
c76c161
4cfff17
79be066
ec237df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Xml; | ||
using Dynamo.Configuration; | ||
using Dynamo.Graph.Nodes; | ||
|
@@ -10,7 +11,6 @@ | |
using Dynamo.Utilities; | ||
using DynamoUtilities; | ||
using Lucene.Net.Documents; | ||
using Lucene.Net.Index; | ||
using Lucene.Net.QueryParsers.Classic; | ||
using Lucene.Net.Search; | ||
|
||
|
@@ -231,29 +231,42 @@ internal string ProcessNodeCategory(string category, ref SearchElementGroup grou | |
return category.Substring(0, index); | ||
} | ||
|
||
internal IEnumerable<NodeSearchElement> Search(string search, LuceneSearchUtility luceneSearchUtility) | ||
/// <summary> | ||
/// Search for nodes by using a search key. | ||
/// </summary> | ||
/// <param name="search"></param> | ||
/// <param name="luceneSearchUtility"></param> | ||
/// <param name="ctk">Cancellation token to short circuit the search.</param> | ||
/// <returns></returns> | ||
/// <exception cref="Exception"></exception> | ||
internal IEnumerable<NodeSearchElement> Search(string search, LuceneSearchUtility luceneSearchUtility, CancellationToken ctk = default) | ||
{ | ||
|
||
ctk.ThrowIfCancellationRequested(); | ||
|
||
if (luceneSearchUtility != null) | ||
{ | ||
//The DirectoryReader and IndexSearcher have to be assigned after commiting indexing changes and before executing the Searcher.Search() method, otherwise new indexed info won't be reflected | ||
luceneSearchUtility.dirReader = luceneSearchUtility.writer != null ? luceneSearchUtility.writer.GetReader(applyAllDeletes: true) : DirectoryReader.Open(luceneSearchUtility.indexDir); | ||
luceneSearchUtility.Searcher = new IndexSearcher(luceneSearchUtility.dirReader); | ||
if (luceneSearchUtility.Searcher == null) | ||
{ | ||
throw new Exception("Invalid IndexSearcher found"); | ||
} | ||
|
||
string searchTerm = search.Trim(); | ||
var candidates = new List<NodeSearchElement>(); | ||
|
||
var parser = new MultiFieldQueryParser(LuceneConfig.LuceneNetVersion, LuceneConfig.NodeIndexFields, luceneSearchUtility.Analyzer) | ||
{ | ||
AllowLeadingWildcard = true, | ||
DefaultOperator = LuceneConfig.DefaultOperator, | ||
FuzzyMinSim = LuceneConfig.MinimumSimilarity | ||
}; | ||
|
||
Query query = parser.Parse(luceneSearchUtility.CreateSearchQuery(LuceneConfig.NodeIndexFields, searchTerm)); | ||
Query query = parser.Parse(luceneSearchUtility.CreateSearchQuery(LuceneConfig.NodeIndexFields, searchTerm, false, ctk)); | ||
TopDocs topDocs = luceneSearchUtility.Searcher.Search(query, n: LuceneConfig.DefaultResultsCount); | ||
|
||
for (int i = 0; i < topDocs.ScoreDocs.Length; i++) | ||
{ | ||
ctk.ThrowIfCancellationRequested(); | ||
|
||
// read back a Lucene doc from results | ||
Document resultDoc = luceneSearchUtility.Searcher.Doc(topDocs.ScoreDocs[i].Doc); | ||
|
||
|
@@ -268,7 +281,7 @@ internal IEnumerable<NodeSearchElement> Search(string search, LuceneSearchUtilit | |
} | ||
else | ||
{ | ||
var foundNode = FindModelForNodeNameAndCategory(name, cat, parameters); | ||
var foundNode = FindModelForNodeNameAndCategory(name, cat, parameters, ctk); | ||
if (foundNode != null) | ||
{ | ||
candidates.Add(foundNode); | ||
|
@@ -280,11 +293,20 @@ internal IEnumerable<NodeSearchElement> Search(string search, LuceneSearchUtilit | |
return null; | ||
} | ||
|
||
internal NodeSearchElement FindModelForNodeNameAndCategory(string nodeName, string nodeCategory, string parameters) | ||
/// <summary> | ||
/// Finds the node model that corresponds to the input nodeName, nodeCategory and parameters. | ||
/// </summary> | ||
/// <param name="nodeName"></param> | ||
/// <param name="nodeCategory"></param> | ||
/// <param name="parameters"></param> | ||
/// <param name="ctk">Cancellation token to short circuit the operation.</param> | ||
/// <returns></returns> | ||
internal NodeSearchElement FindModelForNodeNameAndCategory(string nodeName, string nodeCategory, string parameters, CancellationToken ctk = default) | ||
{ | ||
var result = Entries.Where(e => { | ||
if (e.Name.Replace(" ", string.Empty).Equals(nodeName) && e.FullCategoryName.Equals(nodeCategory)) | ||
{ | ||
ctk.ThrowIfCancellationRequested(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is this caught? Why can't the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to short circuit the Where operation as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will put on at the beginning too |
||
//When the node info was indexed if Parameters was null we added an empty space (null cannot be indexed) | ||
//Then in this case when searching if e.Parameters is null we need to check against empty space | ||
if (e.Parameters == null) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Threading; | ||
using Dynamo.Configuration; | ||
using Dynamo.Models; | ||
using Dynamo.Search.SearchElements; | ||
|
@@ -23,7 +24,6 @@ | |
using Lucene.Net.Search; | ||
using Lucene.Net.Store; | ||
using Lucene.Net.Util; | ||
using Newtonsoft.Json; | ||
|
||
namespace Dynamo.Utilities | ||
{ | ||
|
@@ -190,6 +190,12 @@ internal void CreateLuceneIndexWriter(OpenMode mode = OpenMode.CREATE) | |
} | ||
} | ||
|
||
private void InitializeIndexSearcher() | ||
{ | ||
dirReader = writer != null ? writer.GetReader(applyAllDeletes: true) : DirectoryReader.Open(indexDir); | ||
Searcher = new(dirReader); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if I should lock this, expecting concurrent access to |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RobertGlobant20 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, I think all of them were covered. |
||
/// <summary> | ||
/// Initialize Lucene index document object for reuse | ||
/// </summary> | ||
|
@@ -256,7 +262,8 @@ internal void UpdateIndexedNodesInfo(List<NodeSearchElement> nodeList) | |
{ | ||
var iDoc = InitializeIndexDocumentForNodes(); | ||
AddNodeTypeToSearchIndex(node, iDoc); | ||
} | ||
} | ||
InitializeIndexSearcher(); | ||
} | ||
} | ||
|
||
|
@@ -321,8 +328,9 @@ internal void SetDocumentFieldValue(Document doc, string field, string value, bo | |
/// <param name="fields">All fields to be searched in.</param> | ||
/// <param name="SearchTerm">Search key to be searched for.</param> | ||
/// <param name="IsPackageContext">Set this to true if the search context is packages instead of nodes.</param> | ||
/// <param name="ctk">Cancellation token to short circuit the search.</param> | ||
/// <returns></returns> | ||
internal string CreateSearchQuery(string[] fields, string SearchTerm, bool IsPackageContext = false) | ||
internal string CreateSearchQuery(string[] fields, string SearchTerm, bool IsPackageContext = false, CancellationToken ctk = default) | ||
{ | ||
//By Default the search will be normal | ||
SearchType searchType = SearchType.Normal; | ||
|
@@ -353,6 +361,7 @@ internal string CreateSearchQuery(string[] fields, string SearchTerm, bool IsPac | |
foreach (string f in fields) | ||
{ | ||
Occur occurQuery = Occur.SHOULD; | ||
ctk.ThrowIfCancellationRequested(); | ||
|
||
searchTerm = QueryParser.Escape(SearchTerm); | ||
if (searchType == SearchType.ByDotCategory) | ||
|
@@ -383,7 +392,7 @@ internal string CreateSearchQuery(string[] fields, string SearchTerm, bool IsPac | |
continue; | ||
|
||
//Adds the FuzzyQuery and 4 WildcardQueries (3 of them contain regular expressions), with the normal weights | ||
AddQueries(searchTerm, f, searchType, booleanQuery, occurQuery, fuzzyLogicMaxEdits); | ||
AddQueries(searchTerm, f, searchType, booleanQuery, occurQuery, fuzzyLogicMaxEdits, ctk); | ||
|
||
if (searchType == SearchType.ByEmptySpace) | ||
{ | ||
|
@@ -396,7 +405,7 @@ internal string CreateSearchQuery(string[] fields, string SearchTerm, bool IsPac | |
if (string.IsNullOrEmpty(s)) continue; | ||
|
||
//Adds the FuzzyQuery and 4 WildcardQueries (3 of them contain regular expressions), with the weights for Queries with RegularExpressions | ||
AddQueries(s, f, searchType, booleanQuery, occurQuery, LuceneConfig.FuzzySearchMinEdits, true); | ||
AddQueries(s, f, searchType, booleanQuery, occurQuery, LuceneConfig.FuzzySearchMinEdits, ctk, true); | ||
} | ||
} | ||
} | ||
|
@@ -412,9 +421,12 @@ internal string CreateSearchQuery(string[] fields, string SearchTerm, bool IsPac | |
/// <param name="booleanQuery">The Boolean query in which the Wildcard queries will be added</param> | ||
/// <param name="occurQuery">Occur type can be Should or Must</param> | ||
/// <param name="fuzzyLogicMaxEdits">Max edit lenght for Fuzzy queries</param> | ||
/// <param name="ctk">Cancellation token to short circuit the operation.</param> | ||
/// <param name="termSplit">Indicates if the SearchTerm has been split by empty space or not</param> | ||
private void AddQueries(string searchTerm, string field, SearchType searchType, BooleanQuery booleanQuery, Occur occurQuery, int fuzzyLogicMaxEdits, bool termSplit = false) | ||
private void AddQueries(string searchTerm, string field, SearchType searchType, BooleanQuery booleanQuery, Occur occurQuery, int fuzzyLogicMaxEdits, CancellationToken ctk = default, bool termSplit = false) | ||
{ | ||
ctk.ThrowIfCancellationRequested(); | ||
|
||
string querySearchTerm = searchTerm.Replace(" ", string.Empty); | ||
|
||
FuzzyQuery fuzzyQuery; | ||
|
@@ -582,6 +594,8 @@ internal void CommitWriterChanges() | |
{ | ||
//Commit the info indexed if index writer exists | ||
writer?.Commit(); | ||
|
||
InitializeIndexSearcher(); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -627,6 +641,7 @@ internal void AddNodeTypeToSearchIndex(NodeSearchElement node, Document doc) | |
SetDocumentFieldValue(doc, nameof(LuceneConfig.NodeFieldsEnum.Parameters), node.Parameters ?? string.Empty); | ||
|
||
writer?.AddDocument(doc); | ||
InitializeIndexSearcher(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this specific call is needed due that usually after a node info is indexed (or all the nodes info were indexed ) we call CommitWriterChanges() - and this call is already calling the InitializeIndexSearcher() method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have a perf test for this |
||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where do all these get caught?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These get caught by the Task object that wraps the parent caller