Added to Thriveopedia aproximate, page content and side panel search - #7061
Added to Thriveopedia aproximate, page content and side panel search#7061TomiYea wants to merge 21 commits into
Conversation
Added Levenshtein distance to help with navigating the thriveopedia Allows searching the body and infobox of wiki pages (currently only those which are children of ThriveopediaWikiPage) Closes Revolutionary-Games#4193 Co-authored-by: Tomás Monteiro <tomasdiasmonteiro@tecnico.ulisboa.pt>
b488284 to
49e8fe4
Compare
Fixed typos from the code Improved memory usage in the search function Fixed formating issues
50b0c10 to
8871aeb
Compare
removed the nullable from ThriveopediaWikiPage.TranslatedPageBody ThriveopediaWikiPage.TranslatedAdditionalSearchContent
| .Contains(newTextLowercase); | ||
| string pagename = page.Key.TranslatedPageName.ToLower(CultureInfo.CurrentCulture); | ||
| string? pagecontent = page.Key.TranslatedPageBody?.ToLower(CultureInfo.CurrentCulture); | ||
| string? adicionalContent = page.Key.TranslatedAdditionalSearchContent?.ToLower(CultureInfo.CurrentCulture); |
There was a problem hiding this comment.
Either a typo or not British English spelling in the name. A correct spelling of "additional" is here in the PR incidentally: https://github.com/Revolutionary-Games/Thrive/pull/7061/changes#diff-17a33871b0d2e73f93be118bc6bedbe0f40dfa3ea7e44e10c4f6e74b6bdbdd86R24
There was a problem hiding this comment.
I commented on all the typos I saw, apparently this had the same typo but I didn't notice in my initial review. So yeah fixing this as well would be good.
Thriveopedia search function is now a background task Slight Thrivepedia performance Added adicional search content to the Species info page
|
The lead programmer for Thrive is currently on vacation until 2026-07-13. Until then other programmers will try to make pull request reviews, but please be patient if your PR is not getting reviewed. PRs may be merged after multiple programmers have approved the changes (especially making sure to ensure style guide conformance and gameplay testing are good). If there are no active experienced programmers who can perform merges, PRs may need to wait until the lead programmer is back to be merged. |
hhyyrylainen
left a comment
There was a problem hiding this comment.
Here's a quick re-review now that I'm back from my break. I didn't look at the core search algorithm again, but I think it was reasonable the last time I checked it?
| { | ||
| get | ||
| { | ||
| StringBuilder builder = new StringBuilder(); |
There was a problem hiding this comment.
This needs some kind of caching as these kind of getters that build an absolute ton of temporary data are going to be really terrible if fetched more than once.
| StringBuilder builder = new StringBuilder(); | ||
|
|
||
| // Todo:find a way to avoid making new hashsets everytime this is requested | ||
| HashSet<string> organelleNames = new HashSet<string>(); |
There was a problem hiding this comment.
Very good TODO observation here as well, this should be fixed before we consider merging this.
| searchThrottling = NodePath("SearchThrottling") | ||
| homePage = NodePath("MarginContainer/VBoxContainer/HBoxContainer3/MarginContainer/ThriveopediaHomePage") | ||
|
|
||
| [node name="SearchThrottling" type="Timer" parent="." unique_id=1395351612] |
There was a problem hiding this comment.
Rather than using timer nodes, we usually just keep track of elapsed time in C# code using _Process delta parameter.
| if (parent != null) | ||
| { | ||
| parent.Visible = visible; | ||
| // parent.Visible = visible; |
There was a problem hiding this comment.
SetParentPagesVisibility is currently only being called by DoBackgroundPageSearch which is background work
Directly doing parent.visible would throw errors
There was a problem hiding this comment.
Yeah, I think I saw that in my next comment (#7061 (comment)). So yeah that should be used instead (Invoke) and a comment here explaining why invoking on the main thread is needed would be very nice.
| private void DoBackgroundPageSearch(string newText) | ||
| { | ||
| // stageDropdown.Visible = false; | ||
| stageDropdown.SetDeferred(TreeItem.PropertyName.Visible, false); |
There was a problem hiding this comment.
Ah is this about being on the background thread?
If so we have a custom Invoke system that should be used instead for deferred actions that need to happen on the main thread.
improved ThriveopediaWikiPage and ThriveopediaSpeciesInfoPage by cacheing some search context replaced the Timer Node on thriveopdia.tsnc by tracking time directly on the node replaced the set_defered calls in the background search whit a Invoke.queue task SetParentPagesVisibility now sets visibility directly again since visibility task in now on the main thread again
Added search tags for the museum and patch map Fixed formating Added some static searchtags improved ThriveopediaSpeciesInfoPage's hashset allocation by having a static element synced the localization whit master
| } | ||
|
|
||
| [node name="Thriveopedia" type="Control" unique_id=164322853 node_paths=PackedStringArray("backButton", "forwardButton", "pageContainer", "pageTreeContainer", "pageTreeContainerAnim", "pageTitle", "viewOnlineButton", "pageTree", "homePage")] | ||
| [node name="Thriveopedia" type="Control" unique_id=164322853 node_paths=PackedStringArray("backButton", "forwardButton", "pageContainer", "pageTreeContainer", "pageTreeContainerAnim", "pageTitle", "viewOnlineButton", "pageTree", "searchThrottling", "homePage")] |
There was a problem hiding this comment.
It looks like the node "searchThrottling" doesn't exist so shouldn't it be also removed from this change?
hhyyrylainen
left a comment
There was a problem hiding this comment.
I think I saw one bug remaining and a few general Thrive codestyle problems.
Can I ask honestly if you are using an LLM to make PRs? If so are my comments helping you improve your skills at all? If not I kind of unfortunately do not see much point in spending a bunch of time trying to guide you to making high quality Thrive contributions if you cannot really improve.
| { | ||
| builder.AppendLine(Localization.Translate("MULTICELLULAR")); | ||
|
|
||
| foreach (CellTemplate cell in multicellularSpecies.ModifiableGameplayCells) |
There was a problem hiding this comment.
Is it useful to list organelle names in the translation search? I'd say it is not as a big multicellular species can have 20 cells each with like 50 organelles, so this I think unnecessarily stresses the search system.
So basically any species would match any organelle search and I don't really see that being useful. Or what's your thought on that?
There was a problem hiding this comment.
I thought it would be useful for find species which could be toxic like whit oxytoxisome.
But it now seams that the search tags are providing more help
There was a problem hiding this comment.
I think that for truly finding species we would need a special search page for species, and it could have stuff like partial name, size (min, max), stage, has been engulfed by the player, has these organelles, doesn't have these organelles.
So I think it would make more sense to exclude search by organelle name in this PR.
| public override void _Process(double delta) | ||
| { | ||
| searchTimer += delta; | ||
| if (trackSearchTimer && searchTimer > 0.1d) |
There was a problem hiding this comment.
| if (trackSearchTimer && searchTimer > 0.1d) | |
| if (trackSearchTimer && searchTimer > 0.1) |
What is that number format? I'm pretty sure it is not used anywhere else in Thrive.
| } | ||
| else | ||
| { | ||
| searchTimer = 0.0d; |
There was a problem hiding this comment.
| searchTimer = 0.0d; | |
| searchTimer = 0; |
| /// <summary> | ||
| /// The current text to search in the next background search. | ||
| /// </summary> | ||
| private string currSearchText = string.Empty; |
There was a problem hiding this comment.
| private string currSearchText = string.Empty; | |
| private string currentSearchText = string.Empty; |
I'm relatively sure that "curr" is not on our approved abbreviations list so it is not allowed to be used in the code.
|
|
||
| foreach (var page in allPages) | ||
| { | ||
| // todo: maybe switch ToLower whit something else since it does return "a copy" |
There was a problem hiding this comment.
| // todo: maybe switch ToLower whit something else since it does return "a copy" | |
| // TODO: maybe switch ToLower whit something else since it does return "a copy" |
That sucks to hear but no. This is my first contribution to any project and i'm new at this process of contributing. |
|
Oh no, sorry for assuming! We've been recently in the past few months hit with quite many AI PRs and they have been quite a pain to review. And unfortunately your PRs had the same feeling to them. Sorry if my words were too harsh. This does change things, though. I will try to guide your PRs to the finish so that you can gain contributing experience. Hopefully you will stick around (for future PRs as well). It is extremely nice to see a new contributor who isn't just feeding us LLM output. And hopefully you can gain experience and level up your programming skills. |
Fixed style issues Removed a unesecesary adicional search context search
Removed a cache that is now goes unused
hhyyrylainen
left a comment
There was a problem hiding this comment.
I tested in the game and this feature seems to work, however I think maybe the search threshold needs tuning. I tried searching for "melanosome" and the "Museum" page appears and disappears multiple times while searching and "melanos" for some reason brings up the "Mechanics" page but I can't really find a reason for that appearing. So this feature is starting to be pretty good but needs tuning.
Besides that I found just a few quite minor code problems, however I think I saw one potential multithreading race condition which can cause a rare search not updating bug.
| private Stage currentSelectedStage; | ||
|
|
||
| /// <summary> | ||
| /// Has the input field changed while it still running a background search. |
There was a problem hiding this comment.
| /// Has the input field changed while it still running a background search. | |
| /// Has the input field changed while it still running a background search? |
Would this make the comment a bit clearer?
| var distanceDictionary = new int[allPages.Count]; | ||
| var visibilityDictionary = new bool[allPages.Count]; |
There was a problem hiding this comment.
Aren't these arrays? So it's a bit confusing I think to have "dictionary" in the name.
Also couldn't these be fields stored in this class? That way they can be reused and only need to be reallocated if the pages count changes.
hhyyrylainen
left a comment
There was a problem hiding this comment.
There's still some code issues (including a search flag race condition still) and I think the search fuzzy match is still not that well working.
If I search for "pri" then "Home" page is not found, but if I search for "prim" then it is and again if I search for "primum" then the "home" page disappears. So functionally I think it looks quite weird that pages disappear and reappear multiple times when you are typing a single word. I don't know enough about text search implementations to suggest a solution to this specific problem.
|
I ended up reviewing the AI review notes and putting in my own thoughts and removing one thing that wasn't really right, so I don't want to just delete my notes so here's the full thing (note thought that I put in some caveats so if following these suggestions I think you should read them all before starting implementing any): PR review findings1. Background search reads and writes shared mutable fields without synchronization
private bool requestingNewSearch;
private bool runningBackgroundSearch;
private bool isSearchDirty;
private string currentSearchText = string.Empty;
private int[] searchDistanceArray = Array.Empty<int>();
private bool[] searchDistancevisibility = Array.Empty<bool>();Specific problems:
The array reuse is especially risky. If another search starts before the queued UI callback has consumed Concrete bugThis part captures the field array indirectly: Invoke.Instance.Queue(() =>
{
iterator = 0;
foreach (var page in allPages)
{
bool isVisible = searchDistancevisibility[iterator];
// ...
}
});The callback does not capture a snapshot of results. It reads Recommendation: use a local result array/list per search and capture that in the queued callback. 2. Out-of-order UI updates are possibleEven if the array issue is fixed, this design can still apply stale search results. Sequence:
There should be a monotonically increasing search version / generation id. The UI callback should only apply results if it is still the latest search. Example concept: private int searchGeneration;Then increment it when the query changes, pass the captured generation into the background search, and ignore queued results if the generation no longer matches. 3.
|
add mutex and locks to avoid race conditions from background search and main thread renamed some fields and arguments tweaked the distance calculation and threshold for the search fixed some captured variables
|
The current commit is a work in progress from the review. |
| else | ||
| { | ||
| SetParentPagesVisibility(page.Value, true); | ||
| runningBackgroundSearch = false; |
There was a problem hiding this comment.
I think there is still a possibility of the data getting out of sync because the invoke queue may happen after this code, and it thus would allow the arrays lock to be grabbed before the invoke runs. I think.
So my suggestion is to move this setting false inside the invoke, that ensures that a new search can only begin after the results of the previous one are applied. Also the main thread potentially gets blocked for the entire duration of the search, if a new search operation was to start before the invoke runs. So for that reason as well I think this flag reset should be inside the invoke so that nothing can grab the array lock and stall the main thread.
| { | ||
| stageDropdown.Visible = true; | ||
| SetParentPagesVisibility(stageDropdown, true); | ||
| bool isVisible = visibilityArray[iterator]; |
There was a problem hiding this comment.
I think this needs a safety check, because technically it looks to be possible for a new page to be added before the results of a search are applied, so this array may not match the size of the pages count.
| iterator = 0; | ||
| foreach (var page in allPages) | ||
| { | ||
| // TODO: maybe switch ToLower whit something else since it does return "a copy" |
There was a problem hiding this comment.
| // TODO: maybe switch ToLower whit something else since it does return "a copy" | |
| // TODO: maybe switch ToLower with something else since it does return "a copy" |
|
I did a new look and I think the safety is much better now. Regarding the number 2 point in the review and what I just commented on: it looks to be possible to get data out of order: because the results are invoke'd it means that they are not immediately applied, so a new search has a chance to start before the results of the old one are applied, and now with the lock that will stall the main thread and it will then apply the results of the new search twice in a row. I think that bug can be removed by ensuring that only after applying the results the flag to allow a new search to begin is reset. This way a partial search cannot be interfered with by a new one starting. That's mostly also the gist of the older review point 2 (interleaving of results from different searches). |
Made the process of beginning a new background search only happen the invoke is done Removed a mutex since the resources used are only used by the main thread
|
While working on the latest commit i notice something that might by a huge problem. |
I think sorting by relevancy would be pretty good. Or alternative and idea I just had is that if a page title matches (or there's good results already), then the page body search would be skipped as they are likely less relevant results. I think that would help in finding the nucleus. Sorting the tree items might be somewhat complicated as it could have quite unexpected bugs show up. |
made the results of a search exclude auxiliary results if the title was sufficient
|
So is this now functionally ready? |
Brief Description of What This PR Does
This PR updates the Thriveopedia search to allow approximate string matching in page names, and also search for the page’s content and side panel info.
Related Issues
Closes #4193
Progress Checklist
Note: before starting this checklist the PR should be marked as non-draft.
break existing features:
https://wiki.revolutionarygamesstudio.com/wiki/Testing_Checklist
(this is important as to not waste the time of Thrive team
members reviewing this PR). This includes gameplay testing by the PR author.
styleguide.
Before merging all CI jobs should finish on this PR without errors, if
there are automatically detected style issues they should be fixed by
the PR author. Merging must follow our
styleguide.