Asset Catalogs: undo stack for catalog edits
Add an undo stack for catalog edits. This only implements the backend, no operators or UI yet. A bunch of `this->xxx` has been replaced by `catalog_collection_->xxx`. Things are getting a bit long, and the class is turning into a god object; refactoring the class is tracked in T92114. Reviewed By: Severin Maniphest Tasks: T92047 Differential Revision: https://developer.blender.org/D12825
This commit is contained in:
parent
b67a937394
commit
a06435e43a
Notes:
blender-bot
2023-02-13 17:28:49 +01:00
Referenced by issue #92047, Undo system for catalog edits
|
@ -60,7 +60,7 @@ class AssetCatalogService {
|
|||
static const CatalogFilePath DEFAULT_CATALOG_FILENAME;
|
||||
|
||||
public:
|
||||
AssetCatalogService() = default;
|
||||
AssetCatalogService();
|
||||
explicit AssetCatalogService(const CatalogFilePath &asset_library_root);
|
||||
|
||||
/** Load asset catalog definitions from the files found in the asset library. */
|
||||
|
@ -143,14 +143,30 @@ class AssetCatalogService {
|
|||
/** Return true only if there are no catalogs known. */
|
||||
bool is_empty() const;
|
||||
|
||||
/**
|
||||
* Store the current catalogs in the undo stack.
|
||||
* This snapshots everything in the #AssetCatalogCollection. */
|
||||
void store_undo_snapshot();
|
||||
/**
|
||||
* Restore the last-saved undo snapshot, pushing the current state onto the redo stack.
|
||||
* The caller is responsible for first checking that undoing is possible.
|
||||
*/
|
||||
void undo();
|
||||
bool is_undo_possbile() const;
|
||||
/**
|
||||
* Restore the last-saved redo snapshot, pushing the current state onto the undo stack.
|
||||
* The caller is responsible for first checking that undoing is possible. */
|
||||
void redo();
|
||||
bool is_redo_possbile() const;
|
||||
|
||||
protected:
|
||||
/* These pointers are owned by this AssetCatalogService. */
|
||||
OwningAssetCatalogMap catalogs_;
|
||||
OwningAssetCatalogMap deleted_catalogs_;
|
||||
std::unique_ptr<AssetCatalogDefinitionFile> catalog_definition_file_;
|
||||
std::unique_ptr<AssetCatalogCollection> catalog_collection_;
|
||||
std::unique_ptr<AssetCatalogTree> catalog_tree_ = std::make_unique<AssetCatalogTree>();
|
||||
CatalogFilePath asset_library_root_;
|
||||
|
||||
Vector<std::unique_ptr<AssetCatalogCollection>> undo_snapshots_;
|
||||
Vector<std::unique_ptr<AssetCatalogCollection>> redo_snapshots_;
|
||||
|
||||
void load_directory_recursive(const CatalogFilePath &directory_path);
|
||||
void load_single_file(const CatalogFilePath &catalog_definition_file_path);
|
||||
|
||||
|
@ -179,6 +195,41 @@ class AssetCatalogService {
|
|||
* For every catalog, ensure that its parent path also has a known catalog.
|
||||
*/
|
||||
void create_missing_catalogs();
|
||||
|
||||
/* For access by subclasses, as those will not be marked as friend by #AssetCatalogCollection. */
|
||||
AssetCatalogDefinitionFile *get_catalog_definition_file();
|
||||
OwningAssetCatalogMap &get_catalogs();
|
||||
};
|
||||
|
||||
/**
|
||||
* All catalogs that are owned by a single asset library, and managed by a single instance of
|
||||
* #AssetCatalogService. The undo system for asset catalog edits contains historical copies of this
|
||||
* struct.
|
||||
*/
|
||||
class AssetCatalogCollection {
|
||||
friend AssetCatalogService;
|
||||
|
||||
public:
|
||||
AssetCatalogCollection() = default;
|
||||
AssetCatalogCollection(const AssetCatalogCollection &other) = delete;
|
||||
AssetCatalogCollection(AssetCatalogCollection &&other) noexcept = default;
|
||||
|
||||
std::unique_ptr<AssetCatalogCollection> deep_copy() const;
|
||||
|
||||
protected:
|
||||
/** All catalogs known, except the known-but-deleted ones. */
|
||||
OwningAssetCatalogMap catalogs_;
|
||||
|
||||
/** Catalogs that have been deleted. They are kept around so that the load-merge-save of catalog
|
||||
* definition files can actually delete them if they already existed on disk (instead of the
|
||||
* merge operation resurrecting them). */
|
||||
OwningAssetCatalogMap deleted_catalogs_;
|
||||
|
||||
/* For now only a single catalog definition file is supported.
|
||||
* The aim is to support an arbitrary number of such files per asset library in the future. */
|
||||
std::unique_ptr<AssetCatalogDefinitionFile> catalog_definition_file_;
|
||||
|
||||
static OwningAssetCatalogMap copy_catalog_map(const OwningAssetCatalogMap &orig);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -292,6 +343,9 @@ class AssetCatalogDefinitionFile {
|
|||
void parse_catalog_file(const CatalogFilePath &catalog_definition_file_path,
|
||||
AssetCatalogParsedFn callback);
|
||||
|
||||
std::unique_ptr<AssetCatalogDefinitionFile> copy_and_remap(
|
||||
const OwningAssetCatalogMap &catalogs, const OwningAssetCatalogMap &deleted_catalogs) const;
|
||||
|
||||
protected:
|
||||
/* Catalogs stored in this file. They are mapped by ID to make it possible to query whether a
|
||||
* catalog is already known, without having to find the corresponding `AssetCatalog*`. */
|
||||
|
|
|
@ -55,19 +55,36 @@ const std::string AssetCatalogDefinitionFile::HEADER =
|
|||
"# The first non-ignored line should be the version indicator.\n"
|
||||
"# Other lines are of the format \"UUID:catalog/path/for/assets:simple catalog name\"\n";
|
||||
|
||||
AssetCatalogService::AssetCatalogService()
|
||||
: catalog_collection_(std::make_unique<AssetCatalogCollection>())
|
||||
{
|
||||
}
|
||||
|
||||
AssetCatalogService::AssetCatalogService(const CatalogFilePath &asset_library_root)
|
||||
: asset_library_root_(asset_library_root)
|
||||
: catalog_collection_(std::make_unique<AssetCatalogCollection>()),
|
||||
asset_library_root_(asset_library_root)
|
||||
{
|
||||
}
|
||||
|
||||
bool AssetCatalogService::is_empty() const
|
||||
{
|
||||
return catalogs_.is_empty();
|
||||
return catalog_collection_->catalogs_.is_empty();
|
||||
}
|
||||
|
||||
OwningAssetCatalogMap &AssetCatalogService::get_catalogs()
|
||||
{
|
||||
return catalog_collection_->catalogs_;
|
||||
}
|
||||
|
||||
AssetCatalogDefinitionFile *AssetCatalogService::get_catalog_definition_file()
|
||||
{
|
||||
return catalog_collection_->catalog_definition_file_.get();
|
||||
}
|
||||
|
||||
AssetCatalog *AssetCatalogService::find_catalog(CatalogID catalog_id) const
|
||||
{
|
||||
const std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id);
|
||||
const std::unique_ptr<AssetCatalog> *catalog_uptr_ptr =
|
||||
catalog_collection_->catalogs_.lookup_ptr(catalog_id);
|
||||
if (catalog_uptr_ptr == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -76,7 +93,7 @@ AssetCatalog *AssetCatalogService::find_catalog(CatalogID catalog_id) const
|
|||
|
||||
AssetCatalog *AssetCatalogService::find_catalog_by_path(const AssetCatalogPath &path) const
|
||||
{
|
||||
for (const auto &catalog : catalogs_.values()) {
|
||||
for (const auto &catalog : catalog_collection_->catalogs_.values()) {
|
||||
if (catalog->path == path) {
|
||||
return catalog.get();
|
||||
}
|
||||
|
@ -103,7 +120,7 @@ AssetCatalogFilter AssetCatalogService::create_catalog_filter(
|
|||
* then only do an exact match on the path (instead of the more complex `is_contained_in()`
|
||||
* call). Without an extra indexed-by-path acceleration structure, this is still going to require
|
||||
* a linear search, though. */
|
||||
for (const auto &catalog_uptr : this->catalogs_.values()) {
|
||||
for (const auto &catalog_uptr : catalog_collection_->catalogs_.values()) {
|
||||
if (catalog_uptr->path.is_contained_in(active_catalog->path)) {
|
||||
matching_catalog_ids.add(catalog_uptr->catalog_id);
|
||||
}
|
||||
|
@ -114,7 +131,8 @@ AssetCatalogFilter AssetCatalogService::create_catalog_filter(
|
|||
|
||||
void AssetCatalogService::delete_catalog_by_id(const CatalogID catalog_id)
|
||||
{
|
||||
std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = this->catalogs_.lookup_ptr(catalog_id);
|
||||
std::unique_ptr<AssetCatalog> *catalog_uptr_ptr = catalog_collection_->catalogs_.lookup_ptr(
|
||||
catalog_id);
|
||||
if (catalog_uptr_ptr == nullptr) {
|
||||
/* Catalog cannot be found, which is fine. */
|
||||
return;
|
||||
|
@ -124,18 +142,19 @@ void AssetCatalogService::delete_catalog_by_id(const CatalogID catalog_id)
|
|||
AssetCatalog *catalog = catalog_uptr_ptr->get();
|
||||
catalog->flags.is_deleted = true;
|
||||
|
||||
/* Move ownership from this->catalogs_ to this->deleted_catalogs_. */
|
||||
this->deleted_catalogs_.add(catalog_id, std::move(*catalog_uptr_ptr));
|
||||
/* Move ownership from catalog_collection_->catalogs_ to catalog_collection_->deleted_catalogs_.
|
||||
*/
|
||||
catalog_collection_->deleted_catalogs_.add(catalog_id, std::move(*catalog_uptr_ptr));
|
||||
|
||||
/* The catalog can now be removed from the map without freeing the actual AssetCatalog. */
|
||||
this->catalogs_.remove(catalog_id);
|
||||
catalog_collection_->catalogs_.remove(catalog_id);
|
||||
}
|
||||
|
||||
void AssetCatalogService::prune_catalogs_by_path(const AssetCatalogPath &path)
|
||||
{
|
||||
/* Build a collection of catalog IDs to delete. */
|
||||
Set<CatalogID> catalogs_to_delete;
|
||||
for (const auto &catalog_uptr : this->catalogs_.values()) {
|
||||
for (const auto &catalog_uptr : catalog_collection_->catalogs_.values()) {
|
||||
const AssetCatalog *cat = catalog_uptr.get();
|
||||
if (cat->path.is_contained_in(path)) {
|
||||
catalogs_to_delete.add(cat->catalog_id);
|
||||
|
@ -166,7 +185,7 @@ void AssetCatalogService::update_catalog_path(const CatalogID catalog_id,
|
|||
AssetCatalog *renamed_cat = this->find_catalog(catalog_id);
|
||||
const AssetCatalogPath old_cat_path = renamed_cat->path;
|
||||
|
||||
for (auto &catalog_uptr : catalogs_.values()) {
|
||||
for (auto &catalog_uptr : catalog_collection_->catalogs_.values()) {
|
||||
AssetCatalog *cat = catalog_uptr.get();
|
||||
|
||||
const AssetCatalogPath new_path = cat->path.rebase(old_cat_path, new_catalog_path);
|
||||
|
@ -189,13 +208,14 @@ AssetCatalog *AssetCatalogService::create_catalog(const AssetCatalogPath &catalo
|
|||
/* TODO(@sybren): move the `AssetCatalog::from_path()` function to another place, that can reuse
|
||||
* catalogs when a catalog with the given path is already known, and avoid duplicate catalog IDs.
|
||||
*/
|
||||
BLI_assert_msg(!catalogs_.contains(catalog->catalog_id), "duplicate catalog ID not supported");
|
||||
catalogs_.add_new(catalog->catalog_id, std::move(catalog));
|
||||
BLI_assert_msg(!catalog_collection_->catalogs_.contains(catalog->catalog_id),
|
||||
"duplicate catalog ID not supported");
|
||||
catalog_collection_->catalogs_.add_new(catalog->catalog_id, std::move(catalog));
|
||||
|
||||
if (catalog_definition_file_) {
|
||||
if (catalog_collection_->catalog_definition_file_) {
|
||||
/* Ensure the new catalog gets written to disk at some point. If there is no CDF in memory yet,
|
||||
* it's enough to have the catalog known to the service as it'll be saved to a new file. */
|
||||
catalog_definition_file_->add_new(catalog_ptr);
|
||||
catalog_collection_->catalog_definition_file_->add_new(catalog_ptr);
|
||||
}
|
||||
|
||||
BLI_assert_msg(catalog_tree_, "An Asset Catalog tree should always exist.");
|
||||
|
@ -263,9 +283,9 @@ void AssetCatalogService::load_single_file(const CatalogFilePath &catalog_defini
|
|||
std::unique_ptr<AssetCatalogDefinitionFile> cdf = parse_catalog_file(
|
||||
catalog_definition_file_path);
|
||||
|
||||
BLI_assert_msg(!this->catalog_definition_file_,
|
||||
BLI_assert_msg(!catalog_collection_->catalog_definition_file_,
|
||||
"Only loading of a single catalog definition file is supported.");
|
||||
this->catalog_definition_file_ = std::move(cdf);
|
||||
catalog_collection_->catalog_definition_file_ = std::move(cdf);
|
||||
}
|
||||
|
||||
std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::parse_catalog_file(
|
||||
|
@ -276,7 +296,7 @@ std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::parse_catalog_f
|
|||
|
||||
auto catalog_parsed_callback = [this, catalog_definition_file_path](
|
||||
std::unique_ptr<AssetCatalog> catalog) {
|
||||
if (this->catalogs_.contains(catalog->catalog_id)) {
|
||||
if (catalog_collection_->catalogs_.contains(catalog->catalog_id)) {
|
||||
// TODO(@sybren): apparently another CDF was already loaded. This is not supported yet.
|
||||
std::cerr << catalog_definition_file_path << ": multiple definitions of catalog "
|
||||
<< catalog->catalog_id << " in multiple files, ignoring this one." << std::endl;
|
||||
|
@ -285,7 +305,7 @@ std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::parse_catalog_f
|
|||
}
|
||||
|
||||
/* The AssetCatalog pointer is now owned by the AssetCatalogService. */
|
||||
this->catalogs_.add_new(catalog->catalog_id, std::move(catalog));
|
||||
catalog_collection_->catalogs_.add_new(catalog->catalog_id, std::move(catalog));
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -297,9 +317,8 @@ std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::parse_catalog_f
|
|||
void AssetCatalogService::merge_from_disk_before_writing()
|
||||
{
|
||||
/* TODO(Sybren): expand to support multiple CDFs. */
|
||||
|
||||
if (!catalog_definition_file_ || catalog_definition_file_->file_path.empty() ||
|
||||
!BLI_is_file(catalog_definition_file_->file_path.c_str())) {
|
||||
AssetCatalogDefinitionFile *const cdf = catalog_collection_->catalog_definition_file_.get();
|
||||
if (!cdf || cdf->file_path.empty() || !BLI_is_file(cdf->file_path.c_str())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -308,22 +327,21 @@ void AssetCatalogService::merge_from_disk_before_writing()
|
|||
|
||||
/* The following two conditions could be or'ed together. Keeping them separated helps when
|
||||
* adding debug prints, breakpoints, etc. */
|
||||
if (this->catalogs_.contains(catalog_id)) {
|
||||
if (catalog_collection_->catalogs_.contains(catalog_id)) {
|
||||
/* This catalog was already seen, so just ignore it. */
|
||||
return false;
|
||||
}
|
||||
if (this->deleted_catalogs_.contains(catalog_id)) {
|
||||
if (catalog_collection_->deleted_catalogs_.contains(catalog_id)) {
|
||||
/* This catalog was already seen and subsequently deleted, so just ignore it. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* This is a new catalog, so let's keep it around. */
|
||||
this->catalogs_.add_new(catalog_id, std::move(catalog));
|
||||
catalog_collection_->catalogs_.add_new(catalog_id, std::move(catalog));
|
||||
return true;
|
||||
};
|
||||
|
||||
catalog_definition_file_->parse_catalog_file(catalog_definition_file_->file_path,
|
||||
catalog_parsed_callback);
|
||||
cdf->parse_catalog_file(cdf->file_path, catalog_parsed_callback);
|
||||
}
|
||||
|
||||
bool AssetCatalogService::write_to_disk_on_blendfile_save(const CatalogFilePath &blend_file_path)
|
||||
|
@ -331,20 +349,21 @@ bool AssetCatalogService::write_to_disk_on_blendfile_save(const CatalogFilePath
|
|||
/* TODO(Sybren): expand to support multiple CDFs. */
|
||||
|
||||
/* - Already loaded a CDF from disk? -> Always write to that file. */
|
||||
if (this->catalog_definition_file_) {
|
||||
if (catalog_collection_->catalog_definition_file_) {
|
||||
merge_from_disk_before_writing();
|
||||
return catalog_definition_file_->write_to_disk();
|
||||
return catalog_collection_->catalog_definition_file_->write_to_disk();
|
||||
}
|
||||
|
||||
if (catalogs_.is_empty() && deleted_catalogs_.is_empty()) {
|
||||
if (catalog_collection_->catalogs_.is_empty() &&
|
||||
catalog_collection_->deleted_catalogs_.is_empty()) {
|
||||
/* Avoid saving anything, when there is nothing to save. */
|
||||
return true; /* Writing nothing when there is nothing to write is still a success. */
|
||||
}
|
||||
|
||||
const CatalogFilePath cdf_path_to_write = find_suitable_cdf_path_for_writing(blend_file_path);
|
||||
this->catalog_definition_file_ = construct_cdf_in_memory(cdf_path_to_write);
|
||||
catalog_collection_->catalog_definition_file_ = construct_cdf_in_memory(cdf_path_to_write);
|
||||
merge_from_disk_before_writing();
|
||||
return catalog_definition_file_->write_to_disk();
|
||||
return catalog_collection_->catalog_definition_file_->write_to_disk();
|
||||
}
|
||||
|
||||
CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing(
|
||||
|
@ -382,7 +401,7 @@ std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogService::construct_cdf_i
|
|||
auto cdf = std::make_unique<AssetCatalogDefinitionFile>();
|
||||
cdf->file_path = file_path;
|
||||
|
||||
for (auto &catalog : catalogs_.values()) {
|
||||
for (auto &catalog : catalog_collection_->catalogs_.values()) {
|
||||
cdf->add_new(catalog.get());
|
||||
}
|
||||
|
||||
|
@ -399,7 +418,7 @@ std::unique_ptr<AssetCatalogTree> AssetCatalogService::read_into_tree()
|
|||
auto tree = std::make_unique<AssetCatalogTree>();
|
||||
|
||||
/* Go through the catalogs, insert each path component into the tree where needed. */
|
||||
for (auto &catalog : catalogs_.values()) {
|
||||
for (auto &catalog : catalog_collection_->catalogs_.values()) {
|
||||
tree->insert_item(*catalog);
|
||||
}
|
||||
|
||||
|
@ -416,7 +435,7 @@ 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()) {
|
||||
for (auto &catalog : catalog_collection_->catalogs_.values()) {
|
||||
paths_to_check.insert(catalog->path);
|
||||
}
|
||||
|
||||
|
@ -450,6 +469,68 @@ void AssetCatalogService::create_missing_catalogs()
|
|||
/* TODO(Sybren): bind the newly created catalogs to a CDF, if we know about it. */
|
||||
}
|
||||
|
||||
bool AssetCatalogService::is_undo_possbile() const
|
||||
{
|
||||
return !undo_snapshots_.is_empty();
|
||||
}
|
||||
|
||||
bool AssetCatalogService::is_redo_possbile() const
|
||||
{
|
||||
return !redo_snapshots_.is_empty();
|
||||
}
|
||||
|
||||
void AssetCatalogService::undo()
|
||||
{
|
||||
BLI_assert_msg(is_undo_possbile(), "Undo stack is empty");
|
||||
|
||||
redo_snapshots_.append(std::move(catalog_collection_));
|
||||
catalog_collection_ = std::move(undo_snapshots_.pop_last());
|
||||
}
|
||||
|
||||
void AssetCatalogService::redo()
|
||||
{
|
||||
BLI_assert_msg(is_redo_possbile(), "Redo stack is empty");
|
||||
|
||||
undo_snapshots_.append(std::move(catalog_collection_));
|
||||
catalog_collection_ = std::move(redo_snapshots_.pop_last());
|
||||
}
|
||||
|
||||
void AssetCatalogService::store_undo_snapshot()
|
||||
{
|
||||
std::unique_ptr<AssetCatalogCollection> snapshot = catalog_collection_->deep_copy();
|
||||
undo_snapshots_.append(std::move(snapshot));
|
||||
redo_snapshots_.clear();
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
std::unique_ptr<AssetCatalogCollection> AssetCatalogCollection::deep_copy() const
|
||||
{
|
||||
auto copy = std::make_unique<AssetCatalogCollection>();
|
||||
|
||||
copy->catalogs_ = std::move(copy_catalog_map(this->catalogs_));
|
||||
copy->deleted_catalogs_ = std::move(copy_catalog_map(this->deleted_catalogs_));
|
||||
|
||||
if (catalog_definition_file_) {
|
||||
copy->catalog_definition_file_ = std::move(
|
||||
catalog_definition_file_->copy_and_remap(copy->catalogs_, copy->deleted_catalogs_));
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
OwningAssetCatalogMap AssetCatalogCollection::copy_catalog_map(const OwningAssetCatalogMap &orig)
|
||||
{
|
||||
OwningAssetCatalogMap copy;
|
||||
|
||||
for (const auto &orig_catalog_uptr : orig.values()) {
|
||||
auto copy_catalog_uptr = std::make_unique<AssetCatalog>(*orig_catalog_uptr);
|
||||
copy.add_new(copy_catalog_uptr->catalog_id, std::move(copy_catalog_uptr));
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
AssetCatalogTreeItem::AssetCatalogTreeItem(StringRef name,
|
||||
|
@ -564,6 +645,8 @@ void AssetCatalogTree::foreach_root_item(const ItemIterFn callback)
|
|||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
bool AssetCatalogDefinitionFile::contains(const CatalogID catalog_id) const
|
||||
{
|
||||
return catalogs_.contains(catalog_id);
|
||||
|
@ -782,6 +865,34 @@ bool AssetCatalogDefinitionFile::ensure_directory_exists(
|
|||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogDefinitionFile::copy_and_remap(
|
||||
const OwningAssetCatalogMap &catalogs, const OwningAssetCatalogMap &deleted_catalogs) const
|
||||
{
|
||||
auto copy = std::make_unique<AssetCatalogDefinitionFile>(*this);
|
||||
copy->catalogs_.clear();
|
||||
|
||||
/* Remap pointers of the copy from the original AssetCatalogCollection to the given one. */
|
||||
for (CatalogID catalog_id : catalogs_.keys()) {
|
||||
/* The catalog can be in the regular or the deleted map. */
|
||||
const std::unique_ptr<AssetCatalog> *remapped_catalog_uptr_ptr = catalogs.lookup_ptr(
|
||||
catalog_id);
|
||||
if (remapped_catalog_uptr_ptr) {
|
||||
copy->catalogs_.add_new(catalog_id, remapped_catalog_uptr_ptr->get());
|
||||
continue;
|
||||
}
|
||||
|
||||
remapped_catalog_uptr_ptr = deleted_catalogs.lookup_ptr(catalog_id);
|
||||
if (remapped_catalog_uptr_ptr) {
|
||||
copy->catalogs_.add_new(catalog_id, remapped_catalog_uptr_ptr->get());
|
||||
continue;
|
||||
}
|
||||
|
||||
BLI_assert(!"A CDF should only reference known catalogs.");
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
AssetCatalog::AssetCatalog(const CatalogID catalog_id,
|
||||
const AssetCatalogPath &path,
|
||||
const std::string &simple_name)
|
||||
|
|
|
@ -55,7 +55,7 @@ class TestableAssetCatalogService : public AssetCatalogService {
|
|||
|
||||
AssetCatalogDefinitionFile *get_catalog_definition_file()
|
||||
{
|
||||
return catalog_definition_file_.get();
|
||||
return AssetCatalogService::get_catalog_definition_file();
|
||||
}
|
||||
|
||||
void create_missing_catalogs()
|
||||
|
@ -66,7 +66,7 @@ class TestableAssetCatalogService : public AssetCatalogService {
|
|||
int64_t count_catalogs_with_path(const CatalogFilePath &path)
|
||||
{
|
||||
int64_t count = 0;
|
||||
for (auto &catalog_uptr : catalogs_.values()) {
|
||||
for (auto &catalog_uptr : get_catalogs().values()) {
|
||||
if (catalog_uptr->path == path) {
|
||||
count++;
|
||||
}
|
||||
|
@ -1054,4 +1054,187 @@ TEST_F(AssetCatalogTest, create_catalog_filter_for_unassigned_assets)
|
|||
EXPECT_FALSE(filter.contains(UUID_POSES_ELLIE));
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, cat_collection_deep_copy__empty)
|
||||
{
|
||||
const AssetCatalogCollection empty;
|
||||
auto copy = empty.deep_copy();
|
||||
EXPECT_NE(&empty, copy.get());
|
||||
}
|
||||
|
||||
class TestableAssetCatalogCollection : public AssetCatalogCollection {
|
||||
public:
|
||||
OwningAssetCatalogMap &get_catalogs()
|
||||
{
|
||||
return catalogs_;
|
||||
}
|
||||
OwningAssetCatalogMap &get_deleted_catalogs()
|
||||
{
|
||||
return deleted_catalogs_;
|
||||
}
|
||||
AssetCatalogDefinitionFile *get_catalog_definition_file()
|
||||
{
|
||||
return catalog_definition_file_.get();
|
||||
}
|
||||
AssetCatalogDefinitionFile *allocate_catalog_definition_file()
|
||||
{
|
||||
catalog_definition_file_ = std::make_unique<AssetCatalogDefinitionFile>();
|
||||
return get_catalog_definition_file();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(AssetCatalogTest, cat_collection_deep_copy__nonempty_nocdf)
|
||||
{
|
||||
TestableAssetCatalogCollection catcoll;
|
||||
auto cat1 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA, "poses/Henrik", "");
|
||||
auto cat2 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_FACE, "poses/Henrik/face", "");
|
||||
auto cat3 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_HAND, "poses/Henrik/hands", "");
|
||||
cat3->flags.is_deleted = true;
|
||||
|
||||
AssetCatalog *cat1_ptr = cat1.get();
|
||||
AssetCatalog *cat3_ptr = cat3.get();
|
||||
|
||||
catcoll.get_catalogs().add_new(cat1->catalog_id, std::move(cat1));
|
||||
catcoll.get_catalogs().add_new(cat2->catalog_id, std::move(cat2));
|
||||
catcoll.get_deleted_catalogs().add_new(cat3->catalog_id, std::move(cat3));
|
||||
|
||||
auto copy = catcoll.deep_copy();
|
||||
EXPECT_NE(&catcoll, copy.get());
|
||||
|
||||
TestableAssetCatalogCollection *testcopy = reinterpret_cast<TestableAssetCatalogCollection *>(
|
||||
copy.get());
|
||||
|
||||
/* Test catalogs & deleted catalogs. */
|
||||
EXPECT_EQ(2, testcopy->get_catalogs().size());
|
||||
EXPECT_EQ(1, testcopy->get_deleted_catalogs().size());
|
||||
|
||||
ASSERT_TRUE(testcopy->get_catalogs().contains(UUID_POSES_RUZENA));
|
||||
ASSERT_TRUE(testcopy->get_catalogs().contains(UUID_POSES_RUZENA_FACE));
|
||||
ASSERT_TRUE(testcopy->get_deleted_catalogs().contains(UUID_POSES_RUZENA_HAND));
|
||||
|
||||
EXPECT_NE(nullptr, testcopy->get_catalogs().lookup(UUID_POSES_RUZENA));
|
||||
EXPECT_NE(cat1_ptr, testcopy->get_catalogs().lookup(UUID_POSES_RUZENA).get())
|
||||
<< "AssetCatalogs should be actual copies.";
|
||||
|
||||
EXPECT_NE(nullptr, testcopy->get_deleted_catalogs().lookup(UUID_POSES_RUZENA_HAND));
|
||||
EXPECT_NE(cat3_ptr, testcopy->get_deleted_catalogs().lookup(UUID_POSES_RUZENA_HAND).get())
|
||||
<< "AssetCatalogs should be actual copies.";
|
||||
}
|
||||
|
||||
class TestableAssetCatalogDefinitionFile : public AssetCatalogDefinitionFile {
|
||||
public:
|
||||
Map<CatalogID, AssetCatalog *> get_catalogs()
|
||||
{
|
||||
return catalogs_;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(AssetCatalogTest, cat_collection_deep_copy__nonempty_cdf)
|
||||
{
|
||||
TestableAssetCatalogCollection catcoll;
|
||||
auto cat1 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA, "poses/Henrik", "");
|
||||
auto cat2 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_FACE, "poses/Henrik/face", "");
|
||||
auto cat3 = std::make_unique<AssetCatalog>(UUID_POSES_RUZENA_HAND, "poses/Henrik/hands", "");
|
||||
cat3->flags.is_deleted = true;
|
||||
|
||||
AssetCatalog *cat1_ptr = cat1.get();
|
||||
AssetCatalog *cat2_ptr = cat2.get();
|
||||
AssetCatalog *cat3_ptr = cat3.get();
|
||||
|
||||
catcoll.get_catalogs().add_new(cat1->catalog_id, std::move(cat1));
|
||||
catcoll.get_catalogs().add_new(cat2->catalog_id, std::move(cat2));
|
||||
catcoll.get_deleted_catalogs().add_new(cat3->catalog_id, std::move(cat3));
|
||||
|
||||
AssetCatalogDefinitionFile *cdf = catcoll.allocate_catalog_definition_file();
|
||||
cdf->file_path = "path/to/somewhere.cats.txt";
|
||||
cdf->add_new(cat1_ptr);
|
||||
cdf->add_new(cat2_ptr);
|
||||
cdf->add_new(cat3_ptr);
|
||||
|
||||
/* Test CDF remapping. */
|
||||
auto copy = catcoll.deep_copy();
|
||||
TestableAssetCatalogCollection *testable_copy = static_cast<TestableAssetCatalogCollection *>(
|
||||
copy.get());
|
||||
|
||||
TestableAssetCatalogDefinitionFile *cdf_copy = static_cast<TestableAssetCatalogDefinitionFile *>(
|
||||
testable_copy->get_catalog_definition_file());
|
||||
EXPECT_EQ(testable_copy->get_catalogs().lookup(UUID_POSES_RUZENA).get(),
|
||||
cdf_copy->get_catalogs().lookup(UUID_POSES_RUZENA))
|
||||
<< "AssetCatalog pointers should have been remapped to the copy.";
|
||||
|
||||
EXPECT_EQ(testable_copy->get_deleted_catalogs().lookup(UUID_POSES_RUZENA_HAND).get(),
|
||||
cdf_copy->get_catalogs().lookup(UUID_POSES_RUZENA_HAND))
|
||||
<< "Deleted AssetCatalog pointers should have been remapped to the copy.";
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, undo_redo_one_step)
|
||||
{
|
||||
TestableAssetCatalogService service(asset_library_root_);
|
||||
service.load_from_disk();
|
||||
|
||||
EXPECT_FALSE(service.is_undo_possbile());
|
||||
EXPECT_FALSE(service.is_redo_possbile());
|
||||
|
||||
service.create_catalog("some/catalog/path");
|
||||
EXPECT_FALSE(service.is_undo_possbile())
|
||||
<< "Undo steps should be created explicitly, and not after creating any catalog.";
|
||||
|
||||
service.store_undo_snapshot();
|
||||
const bUUID other_catalog_id = service.create_catalog("other/catalog/path")->catalog_id;
|
||||
EXPECT_TRUE(service.is_undo_possbile())
|
||||
<< "Undo should be possible after creating an undo snapshot.";
|
||||
|
||||
// Undo the creation of the catalog.
|
||||
service.undo();
|
||||
EXPECT_FALSE(service.is_undo_possbile())
|
||||
<< "Undoing the only stored step should make it impossible to undo further.";
|
||||
EXPECT_TRUE(service.is_redo_possbile()) << "Undoing a step should make redo possible.";
|
||||
EXPECT_EQ(nullptr, service.find_catalog_by_path("other/catalog/path"))
|
||||
<< "Undone catalog should not exist after undo.";
|
||||
EXPECT_NE(nullptr, service.find_catalog_by_path("some/catalog/path"))
|
||||
<< "First catalog should still exist after undo.";
|
||||
EXPECT_FALSE(service.get_catalog_definition_file()->contains(other_catalog_id))
|
||||
<< "The CDF should also not contain the undone catalog.";
|
||||
|
||||
// Redo the creation of the catalog.
|
||||
service.redo();
|
||||
EXPECT_TRUE(service.is_undo_possbile())
|
||||
<< "Undoing and then redoing a step should make it possible to undo again.";
|
||||
EXPECT_FALSE(service.is_redo_possbile())
|
||||
<< "Undoing and then redoing a step should make redo impossible.";
|
||||
EXPECT_NE(nullptr, service.find_catalog_by_path("other/catalog/path"))
|
||||
<< "Redone catalog should exist after redo.";
|
||||
EXPECT_NE(nullptr, service.find_catalog_by_path("some/catalog/path"))
|
||||
<< "First catalog should still exist after redo.";
|
||||
EXPECT_TRUE(service.get_catalog_definition_file()->contains(other_catalog_id))
|
||||
<< "The CDF should contain the redone catalog.";
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, undo_redo_more_complex)
|
||||
{
|
||||
TestableAssetCatalogService service(asset_library_root_);
|
||||
service.load_from_disk();
|
||||
|
||||
service.store_undo_snapshot();
|
||||
service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)->simple_name = "Edited simple name";
|
||||
|
||||
service.store_undo_snapshot();
|
||||
service.find_catalog(UUID_POSES_ELLIE)->path = "poselib/EllieWithEditedPath";
|
||||
|
||||
service.undo();
|
||||
service.undo();
|
||||
|
||||
service.store_undo_snapshot();
|
||||
service.find_catalog(UUID_POSES_ELLIE)->simple_name = "Ellie Simple";
|
||||
|
||||
EXPECT_FALSE(service.is_redo_possbile())
|
||||
<< "After storing an undo snapshot, the redo buffer should be empty.";
|
||||
EXPECT_TRUE(service.is_undo_possbile())
|
||||
<< "After storing an undo snapshot, undoing should be possible";
|
||||
|
||||
EXPECT_EQ(service.find_catalog(UUID_POSES_ELLIE)->simple_name, "Ellie Simple"); /* Not undone. */
|
||||
EXPECT_EQ(service.find_catalog(UUID_POSES_ELLIE_WHITESPACE)->simple_name,
|
||||
"POSES_ELLIE WHITESPACE"); /* Undone. */
|
||||
EXPECT_EQ(service.find_catalog(UUID_POSES_ELLIE)->path, "character/Ellie/poselib"); /* Undone. */
|
||||
}
|
||||
|
||||
} // namespace blender::bke::tests
|
||||
|
|
Loading…
Reference in New Issue