Asset Catalogs: create missing parent catalogs
For every known catalog, ensure its parent catalog also exists. This ensures that assets can be assigned to parent catalogs, even when they didn't exist in the Catalog Definition File yet.
This commit is contained in:
@@ -150,6 +150,11 @@ class AssetCatalogService {
|
||||
|
||||
std::unique_ptr<AssetCatalogTree> read_into_tree();
|
||||
void rebuild_tree();
|
||||
|
||||
/**
|
||||
* For every catalog, ensure that its parent path also has a known catalog.
|
||||
*/
|
||||
void create_missing_catalogs();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@@ -111,6 +111,11 @@ class AssetCatalogPath {
|
||||
*/
|
||||
bool is_contained_in(const AssetCatalogPath &other_path) const;
|
||||
|
||||
/**
|
||||
* \return the parent path, or an empty path if there is no parent.
|
||||
*/
|
||||
AssetCatalogPath parent() const;
|
||||
|
||||
/**
|
||||
* Change the initial part of the path from `from_path` to `to_path`.
|
||||
* If this path does not start with `from_path`, return an empty path as result.
|
||||
|
@@ -188,7 +188,7 @@ 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? */
|
||||
|
||||
catalog_tree_ = read_into_tree();
|
||||
rebuild_tree();
|
||||
}
|
||||
|
||||
void AssetCatalogService::load_directory_recursive(const CatalogFilePath &directory_path)
|
||||
@@ -356,9 +356,48 @@ std::unique_ptr<AssetCatalogTree> AssetCatalogService::read_into_tree()
|
||||
|
||||
void AssetCatalogService::rebuild_tree()
|
||||
{
|
||||
create_missing_catalogs();
|
||||
this->catalog_tree_ = read_into_tree();
|
||||
}
|
||||
|
||||
void AssetCatalogService::create_missing_catalogs()
|
||||
{
|
||||
/* Construct an ordered set of paths to check, so that parents are ordered before children. */
|
||||
std::set<AssetCatalogPath> paths_to_check;
|
||||
for (auto &catalog : catalogs_.values()) {
|
||||
paths_to_check.insert(catalog->path);
|
||||
}
|
||||
|
||||
std::set<AssetCatalogPath> seen_paths;
|
||||
/* The empty parent should never be created, so always be considered "seen". */
|
||||
seen_paths.insert(AssetCatalogPath(""));
|
||||
|
||||
/* Find and create missing direct parents (so ignoring parents-of-parents). */
|
||||
while (!paths_to_check.empty()) {
|
||||
/* Pop the first path of the queue. */
|
||||
const AssetCatalogPath path = *paths_to_check.begin();
|
||||
paths_to_check.erase(paths_to_check.begin());
|
||||
|
||||
if (seen_paths.find(path) != seen_paths.end()) {
|
||||
/* This path has been seen already, so it can be ignored. */
|
||||
continue;
|
||||
}
|
||||
seen_paths.insert(path);
|
||||
|
||||
const AssetCatalogPath parent_path = path.parent();
|
||||
if (seen_paths.find(parent_path) != seen_paths.end()) {
|
||||
/* The parent exists, continue to the next path. */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The parent doesn't exist, so create it and queue it up for checking its parent. */
|
||||
create_catalog(parent_path);
|
||||
paths_to_check.insert(parent_path);
|
||||
}
|
||||
|
||||
/* TODO(Sybren): bind the newly created catalogs to a CDF, if we know about it. */
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
AssetCatalogTreeItem::AssetCatalogTreeItem(StringRef name,
|
||||
|
@@ -172,6 +172,18 @@ bool AssetCatalogPath::is_contained_in(const AssetCatalogPath &other_path) const
|
||||
return prefix_ok && next_char == SEPARATOR;
|
||||
}
|
||||
|
||||
AssetCatalogPath AssetCatalogPath::parent() const
|
||||
{
|
||||
if (!*this) {
|
||||
return AssetCatalogPath("");
|
||||
}
|
||||
std::string::size_type last_sep_index = this->path_.rfind(SEPARATOR);
|
||||
if (last_sep_index == std::string::npos) {
|
||||
return AssetCatalogPath("");
|
||||
}
|
||||
return AssetCatalogPath(this->path_.substr(0, last_sep_index));
|
||||
}
|
||||
|
||||
void AssetCatalogPath::iterate_components(ComponentIteratorFn callback) const
|
||||
{
|
||||
const char *next_slash_ptr;
|
||||
|
@@ -231,4 +231,21 @@ TEST(AssetCatalogPathTest, rebase)
|
||||
EXPECT_EQ(empty.rebase("", ""), "");
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, parent)
|
||||
{
|
||||
const AssetCatalogPath ascii_path("path/with/missing/parents");
|
||||
EXPECT_EQ(ascii_path.parent(), "path/with/missing");
|
||||
|
||||
const AssetCatalogPath path("путь/в/Пермь/долог/и/далек");
|
||||
EXPECT_EQ(path.parent(), "путь/в/Пермь/долог/и");
|
||||
EXPECT_EQ(path.parent().parent(), "путь/в/Пермь/долог");
|
||||
EXPECT_EQ(path.parent().parent().parent(), "путь/в/Пермь");
|
||||
|
||||
const AssetCatalogPath one_level("one");
|
||||
EXPECT_EQ(one_level.parent(), "");
|
||||
|
||||
const AssetCatalogPath empty("");
|
||||
EXPECT_EQ(empty.parent(), "");
|
||||
}
|
||||
|
||||
} // namespace blender::bke::tests
|
||||
|
@@ -57,6 +57,11 @@ class TestableAssetCatalogService : public AssetCatalogService {
|
||||
{
|
||||
return catalog_definition_file_.get();
|
||||
}
|
||||
|
||||
void create_missing_catalogs()
|
||||
{
|
||||
AssetCatalogService::create_missing_catalogs();
|
||||
}
|
||||
};
|
||||
|
||||
class AssetCatalogTest : public testing::Test {
|
||||
@@ -846,4 +851,42 @@ TEST_F(AssetCatalogTest, order_by_path)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, create_missing_catalogs)
|
||||
{
|
||||
TestableAssetCatalogService new_service;
|
||||
new_service.create_catalog("path/with/missing/parents");
|
||||
|
||||
EXPECT_EQ(nullptr, new_service.find_catalog_by_path("path/with/missing"))
|
||||
<< "Missing parents should not be immediately created.";
|
||||
EXPECT_EQ(nullptr, new_service.find_catalog_by_path("")) << "Empty path should never be valid";
|
||||
|
||||
new_service.create_missing_catalogs();
|
||||
|
||||
EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with/missing"));
|
||||
EXPECT_NE(nullptr, new_service.find_catalog_by_path("path/with"));
|
||||
EXPECT_NE(nullptr, new_service.find_catalog_by_path("path"));
|
||||
EXPECT_EQ(nullptr, new_service.find_catalog_by_path(""))
|
||||
<< "Empty path should never be valid, even when after missing catalogs";
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, create_missing_catalogs_after_loading)
|
||||
{
|
||||
TestableAssetCatalogService loaded_service(asset_library_root_);
|
||||
loaded_service.load_from_disk();
|
||||
|
||||
const AssetCatalog *cat_char = loaded_service.find_catalog_by_path("character");
|
||||
const AssetCatalog *cat_ellie = loaded_service.find_catalog_by_path("character/Ellie");
|
||||
const AssetCatalog *cat_ruzena = loaded_service.find_catalog_by_path("character/Ružena");
|
||||
ASSERT_NE(nullptr, cat_char) << "Missing parents should be created immediately after loading.";
|
||||
ASSERT_NE(nullptr, cat_ellie) << "Missing parents should be created immediately after loading.";
|
||||
ASSERT_NE(nullptr, cat_ruzena) << "Missing parents should be created immediately after loading.";
|
||||
|
||||
AssetCatalogDefinitionFile *cdf = loaded_service.get_catalog_definition_file();
|
||||
ASSERT_NE(nullptr, cdf);
|
||||
EXPECT_TRUE(cdf->contains(cat_char->catalog_id)) << "Missing parents should be saved to a CDF.";
|
||||
EXPECT_TRUE(cdf->contains(cat_ellie->catalog_id)) << "Missing parents should be saved to a CDF.";
|
||||
EXPECT_TRUE(cdf->contains(cat_ruzena->catalog_id))
|
||||
<< "Missing parents should be saved to a CDF.";
|
||||
}
|
||||
|
||||
} // namespace blender::bke::tests
|
||||
|
Reference in New Issue
Block a user