I use SearchService.CreateContext to create a search and want to check whether an given asset match this seach. Because I want to use seach to select a group of assets. And in AssetPostprocessor to check whether the given asset is match this group, as a filter to do some post process.
But I can not find a fast way to do this, but to read all results from the SearchContext and check whether the given asset contains.
Hi! If all you want to do is check if an asset is part of another group of asset, maybe you could store the result of your search into a HashSet and store the instance ids of the results. Then in your AssetPostProcessor you can check if the instance id of the modified asset is part of the HashSet.
Thanks, I will try it. But in this way the search and AssetPostProcessor will divide into two different part. I need store this HashSet as a global variable so something. And invalidate this cache when it dirty(maybe use GlobalArtifactDependencyVersion ), or I can rely on the cache of search it self?
And the search is slow, which compare to AssetDatabase.FindAssets. It slower 10 times even I use synchronized search mode with a simple search.
If you cache the result of the search into a HashSet, you will definitely need to update the cache yourself.
Can you share your search query, and the size of your project (approximate number of files)? Maybe we can find a better query that would improve the speed and still fit your need.
I build a ‘SearchTest’ project to test the performance. And I test 3 methods: AssetDatabase.FindAssets, sync and async SearchService.Request.
I create a tool script ‘AssetGenerator.cs’ to pollute 5000 empty prefab inside a folder and execute the search test.
The test file as below:
public static class SearchBenchmark
{
[MenuItem("Search Test/Request Script Reload")]
private static void RequestScriptReload()
{
EditorUtility.RequestScriptReload();
}
[MenuItem("Search Test/Benchmark Search")]
private static void PolluteAssets()
{
var start = Time.realtimeSinceStartupAsDouble;
var result1 = AssetDatabase.FindAssets("prefab_182");
var end = Time.realtimeSinceStartupAsDouble;
Debug.Log($"AssetDatabase find count: {result1.Length}, time: {end - start}");
foreach (var i in result1)
{
Debug.Log(AssetDatabase.GUIDToAssetPath(i));
}
start = Time.realtimeSinceStartupAsDouble;
var result2 = SearchService.Request("prefab_182", SearchFlags.Synchronous).Fetch().ToArray();
end = Time.realtimeSinceStartupAsDouble;
Debug.Log($"SearchService sync-find count: {result2.Length}, time: {end - start}");
foreach (var i in result2)
{
Debug.Log(i.context);
}
start = Time.realtimeSinceStartupAsDouble;
SearchService.Request("prefab_182", (SearchContext context, IList<SearchItem> result3) =>
{
end = Time.realtimeSinceStartupAsDouble;
Debug.Log($"SearchService async-find count: {result3.Count}, time: {end - start}");
foreach (var i in result3)
{
Debug.Log(i.context);
}
}, SearchFlags.WantsMore);
}
}
As the SearchService seems has some memory cache, it better click ‘Search Test/Request Script Reload’ before the ‘Search Test/Benchmark Search’. And if without memory cache, the search in very slow:
It spend nearly 21s, but the AssetDatabase.FindAssets only spend 0,0092s.
8449895–1120838–SearchTest.zip (906 KB)
Hi. I think your 21s is because of the index that was built during that time? If you run the benchmark multiple times, what do you get?
Also, you could refine your search query by using “p: prefab_182”. This will limit the search to the asset provider instead of all the active providers. And I would suggest not using the “WantMore” flag if you don’t need it.
With these modifications, I get the following results:
- Right after ‘Search Test/Request Script Reload’
AssetDatabase find count: 1, time: 0.0113542999999936
SearchService sync-find count: 1, time: 0.138340599999992
SearchService async-find count: 2, time: 0.0874408999999901
- After multiple execution:
AssetDatabase find count: 1, time: 0.00164120000000167
SearchService sync-find count: 1, time: 0.0026239999999973
SearchService async-find count: 2, time: 0.00227809999999806
Yes, it is a little bit slower than AssetDatabase.FindAsset
, but it is much more powerful. But if you only need to find an asset by name or with a simple query like t:texture2d
, you should definitely keep using AssetDatabase.FindAsset
.
Thanks, specify the provider speedup the time really fast.
I find the default preset is still work with AssetDatabase.FindAssets, and the PresetManagerPostProcessor and GetDefaultPresetsForObject like the same problem.
In the SearchTest I still have a question. The async-search always gets two results with the same asset path. No matter with or without the WantsMore, the result always has two. Is there any wrong when I use async API?
BTW, search speed is really importent if I want to implement custom AssetPostProcesser with custom asset filter just like the Default Preset dose.
I find HierarchyProperty and it is public but without documentation. It used to implement the AssetDatabase.FindAssets. Can I use it directly?
// Search in hierarchy.
start = Time.realtimeSinceStartupAsDouble;
var result4 = new List<string>();
var property = new HierarchyProperty("Assets/SearchAssets");
property.SetSearchFilter("prefab_182", (int)SearchableEditorWindow.SearchMode.All);
while (property.Next(null))
{
result4.Add(AssetDatabase.GUIDToAssetPath(property.guid));
}
end = Time.realtimeSinceStartupAsDouble;
Debug.Log($"HierarchyProperty find count: {result4.Count}, time: {end - start}");
foreach (var i in result4)
{
Debug.Log(i);
}
Infact, I hope the SelectedObjectsProperty could be exposed in HierarchyProperty binding…
Some relation question: I add a test for the HierarchyProperty.Find but the result seems confusing.
var property = new HierarchyProperty("Assets/SearchAssets");
property.SetSearchFilter("prefab_182", (int)SearchableEditorWindow.SearchMode.All);
var p182 = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/SearchAssets/prefab_182.prefab");
var p256 = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/SearchAssets/prefab_256.prefab");
var p182Id = p182.GetInstanceID();
var p256Id = p256.GetInstanceID();
Debug.Log($"prefab_182: instanceID: {p182Id}, find: {property.Find(p182Id, null)}, {property.row}");
Debug.Log($"prefab_256: instanceID: {p256Id}, find: {property.Find(p256Id, null)}, {property.row}");
Debug.Log($"find none: instanceID: 0, find: {property.Find(0, null)}");
Debug.Log($"find none: instanceID: 123, find: {property.Find(123, null)}");
I add a filter “prefab_182”, but if I Findthe ‘prefab_182’ or ‘prefab_256’ both return true.
8452724–1121456–SearchTest_HierarchyPropertyFind.zip (907 KB)
There is nothing wrong with your API call. When using SearchService.Request, you will often get duplicate results depending on the providers enabled in your context. And event with just the Asset provider, it can return duplicate results depending on if the indexes were ready to be used or not. If you wish to avoid duplicate, you can compare the SearchItem.id.
I don’t see why not.
I think it’s because the “Find” method doesn’t take into account the search filter. It only check if the instance id is part of its hierarchy tree. I’m not a HierarchyProperty expert, but I think you should use “property.Next” to search. This is how it’s done in the Scene hierarchy.