Refactor: Lazy create & update asset catalog trees

Rebuilding the tree immediately after changes could cause the tree to be
rebuilt multiple times. More importantly, it made it harder to reason
about thread safety, since we would touch the tree within a whole bunch
of API functions. Now tree building is simplified and managed in a
single place, so making the tree building thread safe can be made
trivially in a follow-up.

Note, this means the initial catalog tree building doesn't happen in a
background thread together with loading the asset library and catalogs
anymore. But we would already do all further rebuilds on the main thread
anyway, this shouldn't have any notable impact.
This commit is contained in:
Julian Eisel
2024-02-23 18:36:04 +01:00
parent a5ef7ac3bd
commit 2bfaf94fa7
7 changed files with 46 additions and 45 deletions

View File

@@ -41,7 +41,11 @@ using OwningAssetCatalogMap = Map<CatalogID, std::unique_ptr<AssetCatalog>>;
* directory hierarchy). */
class AssetCatalogService {
std::unique_ptr<AssetCatalogCollection> catalog_collection_;
std::unique_ptr<AssetCatalogTree> catalog_tree_;
/**
* Cached catalog tree storage. Lazy-created by #AssetCatalogService::catalog_tree() (hence
* mutable).
*/
mutable std::unique_ptr<AssetCatalogTree> catalog_tree_;
CatalogFilePath asset_library_root_;
Vector<std::unique_ptr<AssetCatalogCollection>> undo_snapshots_;
@@ -130,15 +134,6 @@ class AssetCatalogService {
*/
void reload_catalogs();
/**
* Make sure the tree is updated to the latest collection of catalogs stored in this service.
* Does not depend on a CDF file being available so this can be called on a service that stores
* catalogs that are not stored in a CDF.
* Most API functions that modify catalog data will trigger this, unless otherwise specified (for
* batch operations).
*/
void rebuild_tree();
/** Return catalog with the given ID. Return nullptr if not found. */
AssetCatalog *find_catalog(CatalogID catalog_id) const;
@@ -183,7 +178,7 @@ class AssetCatalogService {
*/
void update_catalog_path(CatalogID catalog_id, const AssetCatalogPath &new_catalog_path);
const AssetCatalogTree *get_catalog_tree() const;
const AssetCatalogTree &catalog_tree() const;
/** Return true only if there are no catalogs known. */
bool is_empty() const;
@@ -256,6 +251,9 @@ class AssetCatalogService {
const CatalogFilePath &blend_file_path);
std::unique_ptr<AssetCatalogTree> read_into_tree() const;
/** Ensure a #catalog_tree() will update the tree. Must be called whenever the contained user
* visible catalogs change. */
void invalidate_catalog_tree();
/**
* For every catalog, ensure that its parent path also has a known catalog.

View File

@@ -43,8 +43,7 @@ const std::string AssetCatalogDefinitionFile::HEADER =
"# Other lines are of the format \"UUID:catalog/path/for/assets:simple catalog name\"\n";
AssetCatalogService::AssetCatalogService()
: catalog_collection_(std::make_unique<AssetCatalogCollection>()),
catalog_tree_(std::make_unique<AssetCatalogTree>())
: catalog_collection_(std::make_unique<AssetCatalogCollection>())
{
}
@@ -237,7 +236,7 @@ void AssetCatalogService::prune_catalogs_by_path(const AssetCatalogPath &path)
this->delete_catalog_by_id_soft(cat_id);
}
this->rebuild_tree();
this->invalidate_catalog_tree();
AssetLibraryService::get()->tag_all_library_catalogs_dirty();
}
@@ -272,7 +271,8 @@ void AssetCatalogService::update_catalog_path(const CatalogID catalog_id,
* blend file, and update the catalog simple name stored there. */
}
this->rebuild_tree();
this->create_missing_catalogs();
this->invalidate_catalog_tree();
AssetLibraryService::get()->tag_all_library_catalogs_dirty();
}
@@ -297,8 +297,8 @@ AssetCatalog *AssetCatalogService::create_catalog(const AssetCatalogPath &catalo
catalog_collection_->catalog_definition_file_->add_new(catalog_ptr);
}
BLI_assert_msg(catalog_tree_, "An Asset Catalog tree should always exist.");
catalog_tree_->insert_item(*catalog_ptr);
this->create_missing_catalogs();
this->invalidate_catalog_tree();
AssetLibraryService::get()->tag_all_library_catalogs_dirty();
return catalog_ptr;
@@ -340,7 +340,8 @@ void AssetCatalogService::load_from_disk(const CatalogFilePath &file_or_director
/* TODO: Should there be a sanitize step? E.g. to remove catalogs with identical paths? */
this->rebuild_tree();
this->create_missing_catalogs();
this->invalidate_catalog_tree();
}
void AssetCatalogService::add_from_existing(
@@ -439,7 +440,8 @@ void AssetCatalogService::reload_catalogs()
cdf->parse_catalog_file(cdf->file_path, catalog_parsed_callback);
this->purge_catalogs_not_listed(cats_in_file);
this->rebuild_tree();
this->create_missing_catalogs();
this->invalidate_catalog_tree();
}
void AssetCatalogService::purge_catalogs_not_listed(const Set<CatalogID> &catalogs_to_keep)
@@ -488,7 +490,7 @@ bool AssetCatalogService::write_to_disk(const CatalogFilePath &blend_file_path)
}
this->untag_has_unsaved_changes();
this->rebuild_tree();
this->invalidate_catalog_tree();
return true;
}
@@ -574,11 +576,6 @@ std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::construct_cdf_i
return cdf;
}
const AssetCatalogTree *AssetCatalogService::get_catalog_tree() const
{
return catalog_tree_.get();
}
std::unique_ptr<AssetCatalogTree> AssetCatalogService::read_into_tree() const
{
auto tree = std::make_unique<AssetCatalogTree>();
@@ -591,10 +588,17 @@ std::unique_ptr<AssetCatalogTree> AssetCatalogService::read_into_tree() const
return tree;
}
void AssetCatalogService::rebuild_tree()
void AssetCatalogService::invalidate_catalog_tree()
{
this->create_missing_catalogs();
this->catalog_tree_ = this->read_into_tree();
this->catalog_tree_ = nullptr;
}
const AssetCatalogTree &AssetCatalogService::catalog_tree() const
{
if (!catalog_tree_) {
catalog_tree_ = read_into_tree();
}
return *catalog_tree_;
}
void AssetCatalogService::create_missing_catalogs()
@@ -653,7 +657,8 @@ void AssetCatalogService::undo()
redo_snapshots_.append(std::move(catalog_collection_));
catalog_collection_ = undo_snapshots_.pop_last();
this->rebuild_tree();
this->create_missing_catalogs();
this->invalidate_catalog_tree();
AssetLibraryService::get()->tag_all_library_catalogs_dirty();
}
@@ -664,7 +669,8 @@ void AssetCatalogService::redo()
undo_snapshots_.append(std::move(catalog_collection_));
catalog_collection_ = redo_snapshots_.pop_last();
this->rebuild_tree();
this->create_missing_catalogs();
this->invalidate_catalog_tree();
AssetLibraryService::get()->tag_all_library_catalogs_dirty();
}

View File

@@ -56,8 +56,6 @@ void AllAssetLibrary::rebuild_catalogs_from_nested(const bool reload_nested_cata
},
false);
new_catalog_service->rebuild_tree();
this->catalog_service = std::move(new_catalog_service);
catalogs_dirty_ = false;
}

View File

@@ -566,8 +566,8 @@ TEST_F(AssetCatalogTest, delete_catalog_leaf)
"path/without/simplename",
};
const AssetCatalogTree *tree = service.get_catalog_tree();
AssetCatalogTreeTestFunctions::expect_tree_items(*tree, expected_paths);
const AssetCatalogTree &tree = service.catalog_tree();
AssetCatalogTreeTestFunctions::expect_tree_items(tree, expected_paths);
}
TEST_F(AssetCatalogTest, delete_catalog_parent_by_id)
@@ -620,8 +620,8 @@ TEST_F(AssetCatalogTest, delete_catalog_parent_by_path)
"path/without/simplename",
};
const AssetCatalogTree *tree = service.get_catalog_tree();
AssetCatalogTreeTestFunctions::expect_tree_items(*tree, expected_paths);
const AssetCatalogTree &tree = service.catalog_tree();
AssetCatalogTreeTestFunctions::expect_tree_items(tree, expected_paths);
}
TEST_F(AssetCatalogTest, delete_catalog_write_to_disk)

View File

@@ -117,8 +117,8 @@ TEST_F(AssetCatalogTreeTest, load_single_file_into_tree)
"path/without/simplename", /* From CDF. */
};
const AssetCatalogTree *tree = service.get_catalog_tree();
expect_tree_items(*tree, expected_paths);
const AssetCatalogTree &tree = service.catalog_tree();
expect_tree_items(tree, expected_paths);
}
TEST_F(AssetCatalogTreeTest, foreach_in_tree)
@@ -141,8 +141,8 @@ TEST_F(AssetCatalogTreeTest, foreach_in_tree)
service.load_from_disk(asset_library_root_ + SEP_STR + "blender_assets.cats.txt");
std::vector<AssetCatalogPath> expected_root_items{{"character", "path"}};
const AssetCatalogTree *tree = service.get_catalog_tree();
expect_tree_root_items(*tree, expected_root_items);
const AssetCatalogTree &tree = service.catalog_tree();
expect_tree_root_items(tree, expected_root_items);
/* Test if the direct children of the root item are what's expected. */
std::vector<std::vector<AssetCatalogPath>> expected_root_child_items = {
@@ -152,7 +152,7 @@ TEST_F(AssetCatalogTreeTest, foreach_in_tree)
{"path/without"},
};
int i = 0;
tree->foreach_root_item([&expected_root_child_items, &i](const AssetCatalogTreeItem &item) {
tree.foreach_root_item([&expected_root_child_items, &i](const AssetCatalogTreeItem &item) {
expect_tree_item_child_items(item, expected_root_child_items[i]);
i++;
});

View File

@@ -75,7 +75,7 @@ asset_system::AssetCatalogTree build_filtered_catalog_tree(
/* Build catalog tree. */
asset_system::AssetCatalogTree filtered_tree;
const asset_system::AssetCatalogTree &full_tree = *library.catalog_service->get_catalog_tree();
const asset_system::AssetCatalogTree &full_tree = library.catalog_service->catalog_tree();
full_tree.foreach_item([&](const asset_system::AssetCatalogTreeItem &item) {
if (!known_paths.contains(item.catalog_path().str())) {
return;
@@ -135,8 +135,7 @@ AssetItemTree build_filtered_all_catalog_tree(
});
asset_system::AssetCatalogTree catalogs_with_node_assets;
const asset_system::AssetCatalogTree &catalog_tree =
*library->catalog_service->get_catalog_tree();
const asset_system::AssetCatalogTree &catalog_tree = library->catalog_service->catalog_tree();
catalog_tree.foreach_item([&](const asset_system::AssetCatalogTreeItem &item) {
if (assets_per_path.lookup(item.catalog_path()).is_empty()) {
return;

View File

@@ -184,7 +184,7 @@ AssetCatalogTreeView::AssetCatalogTreeView(asset_system::AssetLibrary *library,
: asset_library_(library), params_(params), space_file_(space_file)
{
if (library && library->catalog_service) {
catalog_tree_ = library->catalog_service->get_catalog_tree();
catalog_tree_ = &library->catalog_service->catalog_tree();
}
else {
catalog_tree_ = nullptr;