Asset System: New "weak" asset reference for storing in .blend files #106026

Closed
Bastien Montagne wants to merge 13 commits from mont29:F-pr-105603 into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
21 changed files with 720 additions and 29 deletions

View File

@ -13,6 +13,10 @@
#include <memory>
#include <string>
#include "BLI_string_ref.hh"
struct AssetWeakReference;
namespace blender::asset_system {
class AssetIdentifier {
@ -24,6 +28,8 @@ class AssetIdentifier {
AssetIdentifier(AssetIdentifier &&) = default;
AssetIdentifier(const AssetIdentifier &) = default;
StringRefNull library_relative_identifier() const;
std::string full_path() const;
std::string full_library_path() const;
};

View File

@ -6,6 +6,7 @@
#pragma once
struct AssetWeakReference;
struct IDRemapper;
#ifdef __cplusplus
@ -30,7 +31,7 @@ void AS_asset_libraries_exit(void);
*
* To get the in-memory-only "current file" asset library, pass an empty path.
*/
struct AssetLibrary *AS_asset_library_load(const char *library_path);
struct AssetLibrary *AS_asset_library_load(const char *name, const char *library_path);
/** Look up the asset's catalog and copy its simple name into #asset_data. */
void AS_asset_library_refresh_catalog_simplename(struct AssetLibrary *asset_library,
@ -43,6 +44,29 @@ bool AS_asset_library_has_any_unsaved_catalogs(void);
* remapped on change (or assets removed as IDs gets removed). */
void AS_asset_library_remap_ids(const struct IDRemapper *mappings);
/**
* Attempt to resolve a full path to an asset based on the currently available (not necessary
* loaded) asset libraries, and split it into it's directory, ID group and ID name components. The
* path is not guaranteed to exist on disk. On failure to resolve the reference, return arguments
* will point to null.
*
* \note Only works for asset libraries on disk (others can't be resolved).
*
* \param r_path_buffer: Buffer to hold the result in on success. Will be the full path with null
* terminators instead of slashes separating the directory, group and name
* components.
* \param r_dir: Returns the .blend file path with native slashes on success. Optional (passing
* null is allowed).
* \param r_group: Returns the ID group such as "Object", "Material" or "Brush". Optional (passing
* null is allowed).
* \param r_name: Returns the ID name on success. Optional (passing null is allowed).
*/
void AS_asset_full_path_explode_from_weak_ref(const struct AssetWeakReference *asset_reference,
char r_path_buffer[1090 /* FILE_MAX_LIBEXTRA */],
char **r_dir,
char **r_group,
char **r_name);
#ifdef __cplusplus
}
#endif

View File

@ -35,6 +35,10 @@ class AssetStorage;
* to also include asset indexes and more.
*/
class AssetLibrary {
eAssetLibraryType library_type_;
/** The name this asset library will be displayed in the UI as. Will also be used as a weak way
* to identify an asset library (e.g. by #AssetWeakReference). */
std::string name_;
/** If this is an asset library on disk, the top-level directory path. Normalized using
* #normalize_directory_path(). Shared pointer so assets can safely point to it, and don't have
* to hold a copy (which is the size of `std::string` + the allocated buffer, if no short string
@ -77,9 +81,13 @@ class AssetLibrary {
public:
/**
* \param name: The name this asset library will be displayed in the UI as. Will also be used as
* a weak way to identify an asset library (e.g. by #AssetWeakReference). Make sure
* this is set for any custom (not builtin) asset library. That is,
* #ASSET_LIBRARY_CUSTOM ones.
* \param root_path: If this is an asset library on disk, the top-level directory path.
*/
AssetLibrary(StringRef root_path = "");
AssetLibrary(eAssetLibraryType library_type, StringRef name = "", StringRef root_path = "");
~AssetLibrary();
/**
@ -147,6 +155,8 @@ class AssetLibrary {
*/
AssetIdentifier asset_identifier_from_library(StringRef relative_asset_path);
eAssetLibraryType library_type() const;
StringRefNull name() const;
StringRefNull root_path() const;
};

View File

@ -13,6 +13,7 @@ extern "C" {
#endif
struct AssetMetaData;
struct AssetWeakReference;
/** C handle for #asset_system::AssetRepresentation. */
typedef struct AssetRepresentation AssetRepresentation;
@ -27,6 +28,13 @@ bool AS_asset_representation_is_local_id(const AssetRepresentation *asset) ATTR_
bool AS_asset_representation_is_never_link(const AssetRepresentation *asset)
ATTR_WARN_UNUSED_RESULT;
/**
* C version of #AssetRepresentation::make_weak_reference. Returned pointer needs freeing with
* #MEM_delete() or #BKE_asset_weak_reference_free().
*/
AssetWeakReference *AS_asset_representation_weak_reference_create(const AssetRepresentation *asset)
ATTR_WARN_UNUSED_RESULT;
#ifdef __cplusplus
}
#endif

View File

@ -71,6 +71,12 @@ class AssetRepresentation {
const AssetIdentifier &get_identifier() const;
/** Create a weak reference for this asset that can be written to files, but can break under a
* number of conditions.
* A weak reference can only be created if an asset representation is owned by an asset library.
*/
std::unique_ptr<AssetWeakReference> make_weak_reference() const;
StringRefNull get_name() const;
AssetMetaData &get_metadata() const;
/** Get the import method to use for this asset. A different one may be used if

View File

@ -41,6 +41,7 @@ set(SRC
)
set(LIB
bf_blenkernel
)
@ -53,6 +54,7 @@ if(WITH_GTESTS)
tests/asset_catalog_tree_test.cc
tests/asset_library_service_test.cc
tests/asset_library_test.cc
tests/asset_representation_test.cc
tests/asset_library_test_common.hh
)

View File

@ -4,6 +4,7 @@
* \ingroup asset_system
*/
#include <iostream>
#include <string>
#include "BKE_blendfile.h"
@ -20,6 +21,11 @@ AssetIdentifier::AssetIdentifier(std::shared_ptr<std::string> library_root_path,
{
}
StringRefNull AssetIdentifier::library_relative_identifier() const
{
return relative_asset_path_;
}
std::string AssetIdentifier::full_path() const
{
char path[FILE_MAX];

View File

@ -46,7 +46,7 @@ asset_system::AssetLibrary *AS_asset_library_load(const Main *bmain,
* Loading an asset library at this point only means loading the catalogs. Later on this should
* invoke reading of asset representations too.
*/
struct ::AssetLibrary *AS_asset_library_load(const char *library_path)
struct ::AssetLibrary *AS_asset_library_load(const char *name, const char *library_path)
{
AssetLibraryService *service = AssetLibraryService::get();
asset_system::AssetLibrary *lib;
@ -54,7 +54,7 @@ struct ::AssetLibrary *AS_asset_library_load(const char *library_path)
lib = service->get_asset_library_current_file();
}
else {
lib = service->get_asset_library_on_disk(library_path);
lib = service->get_asset_library_on_disk_custom(name, library_path);
}
return reinterpret_cast<struct ::AssetLibrary *>(lib);
}
@ -127,10 +127,70 @@ void AS_asset_library_remap_ids(const IDRemapper *mappings)
true);
}
void AS_asset_full_path_explode_from_weak_ref(const AssetWeakReference *asset_reference,
char r_path_buffer[1090 /* FILE_MAX_LIBEXTRA */],
char **r_dir,
char **r_group,
char **r_name)
{
AssetLibraryService *service = AssetLibraryService::get();
std::optional<AssetLibraryService::ExplodedPath> exploded =
service->resolve_asset_weak_reference_to_exploded_path(*asset_reference);
if (!exploded) {
if (r_dir) {
*r_dir = nullptr;
}
if (r_group) {
*r_group = nullptr;
}
if (r_name) {
*r_name = nullptr;
}
return;
}
BLI_assert(!exploded->group_component.is_empty());
BLI_assert(!exploded->name_component.is_empty());
BLI_strncpy(r_path_buffer, exploded->full_path.c_str(), 1090 /* FILE_MAX_LIBEXTRA */);
if (!exploded->dir_component.is_empty()) {
r_path_buffer[exploded->dir_component.size()] = '\0';
r_path_buffer[exploded->dir_component.size() + 1 + exploded->group_component.size()] = '\0';
if (r_dir) {
*r_dir = r_path_buffer;
}
if (r_group) {
*r_group = r_path_buffer + exploded->dir_component.size() + 1;
}
if (r_name) {
*r_name = r_path_buffer + exploded->dir_component.size() + 1 +
exploded->group_component.size() + 1;
}
}
else {
r_path_buffer[exploded->group_component.size()] = '\0';
if (r_dir) {
*r_dir = nullptr;
}
if (r_group) {
*r_group = r_path_buffer;
}
if (r_name) {
*r_name = r_path_buffer + exploded->group_component.size() + 1;
}
}
}
namespace blender::asset_system {
AssetLibrary::AssetLibrary(StringRef root_path)
: root_path_(std::make_shared<std::string>(utils::normalize_directory_path(root_path))),
AssetLibrary::AssetLibrary(eAssetLibraryType library_type, StringRef name, StringRef root_path)
: library_type_(library_type),
name_(name),
root_path_(std::make_shared<std::string>(utils::normalize_directory_path(root_path))),
asset_storage_(std::make_unique<AssetStorage>()),
catalog_service(std::make_unique<AssetCatalogService>())
{
@ -252,6 +312,16 @@ void AssetLibrary::refresh_catalog_simplename(struct AssetMetaData *asset_data)
STRNCPY(asset_data->catalog_simple_name, catalog->simple_name.c_str());
}
eAssetLibraryType AssetLibrary::library_type() const
{
return library_type_;
}
StringRefNull AssetLibrary::name() const
{
return name_;
}
StringRefNull AssetLibrary::root_path() const
{
return *root_path_;

View File

@ -7,6 +7,7 @@
#include "BKE_blender.h"
#include "BKE_preferences.h"
#include "BLI_path_util.h"
#include "BLI_string_ref.hh"
#include "DNA_asset_types.h"
@ -67,7 +68,7 @@ AssetLibrary *AssetLibraryService::get_asset_library(
return nullptr;
}
AssetLibrary *library = get_asset_library_on_disk(root_path);
AssetLibrary *library = get_asset_library_on_disk_builtin(type, root_path);
library->import_method_ = ASSET_IMPORT_APPEND_REUSE;
return library;
@ -81,7 +82,7 @@ AssetLibrary *AssetLibraryService::get_asset_library(
/* File wasn't saved yet. */
return get_asset_library_current_file();
}
return get_asset_library_on_disk(root_path);
return get_asset_library_on_disk_builtin(type, root_path);
}
case ASSET_LIBRARY_ALL:
return get_asset_library_all(bmain);
@ -97,7 +98,7 @@ AssetLibrary *AssetLibraryService::get_asset_library(
return nullptr;
}
AssetLibrary *library = get_asset_library_on_disk(root_path);
AssetLibrary *library = get_asset_library_on_disk_custom(custom_library->name, root_path);
library->import_method_ = eAssetImportMethod(custom_library->import_method);
library->may_override_import_method_ = true;
@ -108,7 +109,9 @@ AssetLibrary *AssetLibraryService::get_asset_library(
return nullptr;
}
AssetLibrary *AssetLibraryService::get_asset_library_on_disk(StringRefNull root_path)
AssetLibrary *AssetLibraryService::get_asset_library_on_disk(eAssetLibraryType library_type,
StringRef name,
StringRefNull root_path)
{
BLI_assert_msg(!root_path.is_empty(),
"top level directory must be given for on-disk asset library");
@ -124,7 +127,8 @@ AssetLibrary *AssetLibraryService::get_asset_library_on_disk(StringRefNull root_
return lib;
}
std::unique_ptr lib_uptr = std::make_unique<AssetLibrary>(normalized_root_path);
std::unique_ptr lib_uptr = std::make_unique<AssetLibrary>(
library_type, name, normalized_root_path);
AssetLibrary *lib = lib_uptr.get();
lib->on_blend_save_handler_register();
@ -137,6 +141,24 @@ AssetLibrary *AssetLibraryService::get_asset_library_on_disk(StringRefNull root_
return lib;
}
AssetLibrary *AssetLibraryService::get_asset_library_on_disk_custom(StringRef name,
StringRefNull root_path)
{
return get_asset_library_on_disk(ASSET_LIBRARY_CUSTOM, name, root_path);
}
AssetLibrary *AssetLibraryService::get_asset_library_on_disk_builtin(eAssetLibraryType type,
StringRefNull root_path)
{
BLI_assert_msg(
type != ASSET_LIBRARY_CUSTOM,
"Use `get_asset_library_on_disk_custom()` for libraries of type `ASSET_LIBRARY_CUSTOM`");
/* Builtin asset libraries don't need a name, the #eAssetLibraryType is enough to identify them
* (and doesn't change, unlike the name). */
return get_asset_library_on_disk(type, {}, root_path);
}
AssetLibrary *AssetLibraryService::get_asset_library_current_file()
{
if (current_file_library_) {
@ -145,7 +167,7 @@ AssetLibrary *AssetLibraryService::get_asset_library_current_file()
}
else {
CLOG_INFO(&LOG, 2, "get current file lib (loaded)");
current_file_library_ = std::make_unique<AssetLibrary>();
current_file_library_ = std::make_unique<AssetLibrary>(ASSET_LIBRARY_LOCAL);
current_file_library_->on_blend_save_handler_register();
}
@ -190,7 +212,7 @@ AssetLibrary *AssetLibraryService::get_asset_library_all(const Main *bmain)
}
CLOG_INFO(&LOG, 2, "get all lib (loaded)");
all_library_ = std::make_unique<AssetLibrary>();
all_library_ = std::make_unique<AssetLibrary>(ASSET_LIBRARY_ALL);
/* Don't reload catalogs on this initial read, they've just been loaded above. */
rebuild_all_library(*all_library_, /*reload_catlogs=*/false);
@ -202,6 +224,149 @@ AssetLibrary *AssetLibraryService::get_asset_library_all(const Main *bmain)
return all_library_.get();
}
bUserAssetLibrary *AssetLibraryService::find_custom_preferences_asset_library_from_asset_weak_ref(
const AssetWeakReference &asset_reference)
{
if (!ELEM(asset_reference.asset_library_type, ASSET_LIBRARY_CUSTOM)) {
return nullptr;
}
return BKE_preferences_asset_library_find_from_name(&U,
asset_reference.asset_library_identifier);
}
AssetLibrary *AssetLibraryService::find_loaded_on_disk_asset_library_from_name(
StringRef name) const
{
for (const std::unique_ptr<AssetLibrary> &library : on_disk_libraries_.values()) {
if (library->name_ == name) {
return library.get();
}
}
return nullptr;
}
std::string AssetLibraryService::resolve_asset_weak_reference_to_library_path(
const AssetWeakReference &asset_reference)
{
StringRefNull library_path;
switch (eAssetLibraryType(asset_reference.asset_library_type)) {
case ASSET_LIBRARY_CUSTOM: {
bUserAssetLibrary *custom_lib = find_custom_preferences_asset_library_from_asset_weak_ref(
asset_reference);
if (custom_lib) {
library_path = custom_lib->path;
break;
}
/* A bit of an odd-ball, the API supports loading custom libraries from arbitrary paths (used
* by unit tests). So check all loaded on-disk libraries too. */
AssetLibrary *loaded_custom_lib = find_loaded_on_disk_asset_library_from_name(
asset_reference.asset_library_identifier);
if (!loaded_custom_lib) {
return "";
}
library_path = *loaded_custom_lib->root_path_;
break;
}
case ASSET_LIBRARY_ESSENTIALS:
library_path = essentials_directory_path();
break;
case ASSET_LIBRARY_LOCAL:
case ASSET_LIBRARY_ALL:
return "";
}
std::string normalized_library_path = utils::normalize_path(library_path);
return normalized_library_path;
}
/* TODO currently only works for asset libraries on disk (custom or essentials asset libraries).
* Once there is a proper registry of asset libraries, this could contain an asset library locator
* and/or identifier, so a full path (not necessarily file path) can be built for all asset
* libraries. */
std::string AssetLibraryService::resolve_asset_weak_reference_to_full_path(
const AssetWeakReference &asset_reference)
{
if (asset_reference.relative_asset_identifier[0] == '\0') {
return "";
}
std::string library_path = resolve_asset_weak_reference_to_library_path(asset_reference);
if (library_path.empty()) {
return "";
}
std::string normalized_full_path = utils::normalize_path(
library_path + SEP_STR + asset_reference.relative_asset_identifier);
return normalized_full_path;
}
std::optional<AssetLibraryService::ExplodedPath> AssetLibraryService::
resolve_asset_weak_reference_to_exploded_path(const AssetWeakReference &asset_reference)
{
if (asset_reference.relative_asset_identifier[0] == '\0') {
return {};
}
switch (eAssetLibraryType(asset_reference.asset_library_type)) {
case ASSET_LIBRARY_LOCAL: {
std::string path_in_file = utils::normalize_path(asset_reference.relative_asset_identifier);
const int64_t group_len = int64_t(path_in_file.find(SEP));
ExplodedPath exploded{};
exploded.full_path = path_in_file;
exploded.group_component = StringRef(exploded.full_path).substr(0, group_len);
exploded.name_component = StringRef(exploded.full_path).substr(group_len + 1);
return exploded;
}
case ASSET_LIBRARY_CUSTOM:
case ASSET_LIBRARY_ESSENTIALS: {
std::string full_path = resolve_asset_weak_reference_to_full_path(asset_reference);
if (full_path.empty()) {
return {};
}
const std::vector<std::string> blendfile_extensions = {".blend/", ".blend.gz/", ".ble/"};
size_t blendfile_extension_pos = std::string::npos;
for (const std::string &blendfile_ext : blendfile_extensions) {
size_t ext_pos = full_path.rfind(blendfile_ext);
if (ext_pos != std::string::npos &&
(blendfile_extension_pos < ext_pos || blendfile_extension_pos == std::string::npos)) {
blendfile_extension_pos = ext_pos;
}
}
BLI_assert(blendfile_extension_pos != std::string::npos);
size_t group_pos = full_path.find(SEP, blendfile_extension_pos);
BLI_assert(group_pos != std::string::npos);
size_t name_pos = full_path.find(SEP, group_pos + 1);
BLI_assert(group_pos != std::string::npos);
const int64_t dir_len = int64_t(group_pos);
const int64_t group_len = int64_t(name_pos - group_pos - 1);
ExplodedPath exploded{};
exploded.full_path = full_path;
StringRef full_path_ref = exploded.full_path;
exploded.dir_component = full_path_ref.substr(0, dir_len);
exploded.group_component = full_path_ref.substr(dir_len + 1, group_len);
exploded.name_component = full_path_ref.substr(dir_len + 1 + group_len + 1);
return exploded;
}
case ASSET_LIBRARY_ALL:
return {};
}
return {};
}
bUserAssetLibrary *AssetLibraryService::find_custom_asset_library_from_library_ref(
const AssetLibraryReference &library_reference)
{

View File

@ -6,6 +6,8 @@
#pragma once
#include <optional>
#include "AS_asset_library.hh"
#include "BLI_function_ref.hh"
@ -61,21 +63,46 @@ class AssetLibraryService {
static std::string root_path_from_library_ref(const AssetLibraryReference &library_reference);
static bUserAssetLibrary *find_custom_asset_library_from_library_ref(
const AssetLibraryReference &library_reference);
static bUserAssetLibrary *find_custom_preferences_asset_library_from_asset_weak_ref(
const AssetWeakReference &asset_reference);
AssetLibrary *get_asset_library(const Main *bmain,
const AssetLibraryReference &library_reference);
/**
* Get the given asset library. Opens it (i.e. creates a new AssetLibrary instance) if necessary.
*/
AssetLibrary *get_asset_library_on_disk(StringRefNull top_level_directory);
/** Get an asset library of type #ASSET_LIBRARY_CUSTOM. */
AssetLibrary *get_asset_library_on_disk_custom(StringRef name, StringRefNull root_path);
/** Get a builtin (not user defined) asset library. I.e. a library that is **not** of type
* #ASSET_LIBRARY_CUSTOM. */
AssetLibrary *get_asset_library_on_disk_builtin(eAssetLibraryType type, StringRefNull root_path);
/** Get the "Current File" asset library. */
AssetLibrary *get_asset_library_current_file();
/** Get the "All" asset library, which loads all others and merges them into one. */
AssetLibrary *get_asset_library_all(const Main *bmain);
/** Get a valid library path from the weak reference. Empty if e.g. the reference is to a local
* asset. */
std::string resolve_asset_weak_reference_to_library_path(
const AssetWeakReference &asset_reference);
/* See #AS_asset_full_path_resolve_from_weak_ref(). */
std::string resolve_asset_weak_reference_to_full_path(const AssetWeakReference &asset_reference);
/** Struct to hold results from path explosion functions
* (#resolve_asset_weak_reference_to_exploded_path()). */
struct ExplodedPath {
/* The string buffer containing the fully resolved path, if resolving was successful. */
std::string full_path = "";
/* Reference into the part of #full_path that is the directory path. */
StringRef dir_component = "";
/* Reference into the part of #full_path that is the ID group name ("Object", "Brush", ...). */
StringRef group_component = "";
/* Reference into the part of #full_path that is the ID name. */
StringRef name_component = "";
};
/** Similar to #BKE_blendfile_library_path_explode, returns the full path as
* #resolve_asset_weak_reference_to_library_path, with StringRefs to the `dir` (i.e. blendfile
* path), `group` (i.e. ID type) and `name` (i.e. ID name) parts. */
std::optional<ExplodedPath> resolve_asset_weak_reference_to_exploded_path(
const AssetWeakReference &asset_reference);
/** Returns whether there are any known asset libraries with unsaved catalog edits. */
bool has_any_unsaved_catalogs() const;
@ -87,6 +114,14 @@ class AssetLibraryService {
/** Allocate a new instance of the service and assign it to `instance_`. */
static void allocate_service_instance();
AssetLibrary *find_loaded_on_disk_asset_library_from_name(StringRef name) const;
/**
* Get the given asset library. Opens it (i.e. creates a new AssetLibrary instance) if necessary.
*/
AssetLibrary *get_asset_library_on_disk(eAssetLibraryType library_type,
StringRef name,
StringRefNull top_level_directory);
/**
* Ensure the AssetLibraryService instance is destroyed before a new blend file is loaded.
* This makes memory management simple, and ensures a fresh start for every blend file. */

View File

@ -67,6 +67,15 @@ const AssetIdentifier &AssetRepresentation::get_identifier() const
return identifier_;
}
std::unique_ptr<AssetWeakReference> AssetRepresentation::make_weak_reference() const
{
if (!owner_asset_library_) {
return nullptr;
}
return AssetWeakReference::make_reference(*owner_asset_library_, identifier_);
}
StringRefNull AssetRepresentation::get_name() const
{
if (is_local_id_) {
@ -178,4 +187,12 @@ bool AS_asset_representation_is_local_id(const AssetRepresentation *asset_handle
return asset->is_local_id();
}
AssetWeakReference *AS_asset_representation_weak_reference_create(const AssetRepresentation *asset_handle)
{
const asset_system::AssetRepresentation *asset =
reinterpret_cast<const asset_system::AssetRepresentation *>(asset_handle);
std::unique_ptr<AssetWeakReference> weak_ref = asset->make_weak_reference();
return MEM_new<AssetWeakReference>(__func__, std::move(*weak_ref));
}
/** \} */

View File

@ -27,4 +27,15 @@ std::string normalize_directory_path(StringRef directory)
return std::string(dir_normalized);
}
std::string normalize_path(StringRefNull path)
{
char *buf = BLI_strdupn(path.c_str(), size_t(path.size()));
BLI_path_normalize(nullptr, buf);
BLI_path_slash_native(buf);
std::string normalized_path = buf;
MEM_freeN(buf);
return normalized_path;
}
} // namespace blender::asset_system::utils

View File

@ -16,4 +16,10 @@ namespace blender::asset_system::utils {
*/
std::string normalize_directory_path(StringRef directory);
/**
* Returns a normalized file path, with no maximum length.
* Slashes are converted to native format.
*/
std::string normalize_path(StringRefNull path);
} // namespace blender::asset_system::utils

View File

@ -92,10 +92,12 @@ TEST_F(AssetLibraryServiceTest, get_destroy)
TEST_F(AssetLibraryServiceTest, library_pointers)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const lib = service->get_asset_library_on_disk(asset_library_root_);
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetLibrary *const curfile_lib = service->get_asset_library_current_file();
EXPECT_EQ(lib, service->get_asset_library_on_disk(asset_library_root_))
EXPECT_EQ(lib, service->get_asset_library_on_disk_custom(__func__, asset_library_root_))
<< "Calling twice without destroying in between should return the same instance.";
EXPECT_EQ(curfile_lib, service->get_asset_library_current_file())
<< "Calling twice without destroying in between should return the same instance.";
@ -108,7 +110,9 @@ TEST_F(AssetLibraryServiceTest, library_pointers)
TEST_F(AssetLibraryServiceTest, library_from_reference)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const lib = service->get_asset_library_on_disk(asset_library_root_);
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetLibrary *const curfile_lib = service->get_asset_library_current_file();
AssetLibraryReference ref{};
@ -143,16 +147,19 @@ TEST_F(AssetLibraryServiceTest, library_path_trailing_slashes)
BLI_path_slash_ensure(asset_lib_with_slash, PATH_MAX);
AssetLibrary *const lib_no_slash = service->get_asset_library_on_disk(asset_lib_no_slash);
AssetLibrary *const lib_no_slash = service->get_asset_library_on_disk_custom(__func__,
asset_lib_no_slash);
EXPECT_EQ(lib_no_slash, service->get_asset_library_on_disk(asset_lib_with_slash))
EXPECT_EQ(lib_no_slash,
service->get_asset_library_on_disk_custom(__func__, asset_lib_with_slash))
<< "With or without trailing slash shouldn't matter.";
}
TEST_F(AssetLibraryServiceTest, catalogs_loaded)
{
AssetLibraryService *const service = AssetLibraryService::get();
AssetLibrary *const lib = service->get_asset_library_on_disk(asset_library_root_);
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetCatalogService *const cat_service = lib->catalog_service.get();
const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
@ -166,7 +173,8 @@ TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs)
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Empty AssetLibraryService should have no unsaved catalogs";
AssetLibrary *const lib = service->get_asset_library_on_disk(asset_library_root_);
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetCatalogService *const cat_service = lib->catalog_service.get();
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Unchanged AssetLibrary should have no unsaved catalogs";
@ -198,7 +206,7 @@ TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs_after_write)
ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str()));
AssetLibraryService *const service = AssetLibraryService::get();
AssetLibrary *const lib = service->get_asset_library_on_disk(writable_dir);
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__, writable_dir);
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Unchanged AssetLibrary should have no unsaved catalogs";

View File

@ -43,7 +43,7 @@ TEST_F(AssetLibraryTest, AS_asset_library_load)
/* Load the asset library. */
const std::string library_path = test_files_dir + "/" + "asset_library";
::AssetLibrary *library_c_ptr = AS_asset_library_load(library_path.data());
::AssetLibrary *library_c_ptr = AS_asset_library_load(__func__, library_path.data());
ASSERT_NE(nullptr, library_c_ptr);
/* Check that it can be cast to the C++ type and has a Catalog Service. */
@ -71,7 +71,7 @@ TEST_F(AssetLibraryTest, load_nonexistent_directory)
/* Load the asset library. */
const std::string library_path = test_files_dir + "/" +
"asset_library/this/subdir/does/not/exist";
::AssetLibrary *library_c_ptr = AS_asset_library_load(library_path.data());
::AssetLibrary *library_c_ptr = AS_asset_library_load(__func__, library_path.data());
ASSERT_NE(nullptr, library_c_ptr);
/* Check that it can be cast to the C++ type and has a Catalog Service. */

View File

@ -5,7 +5,10 @@
#include <string>
#include <vector>
#include "asset_library_service.hh"
#include "BKE_appdir.h"
#include "BKE_callbacks.h"
#include "BLI_fileops.h"
#include "BLI_path_util.h"
@ -35,10 +38,13 @@ class AssetLibraryTestBase : public testing::Test {
{
testing::Test::SetUpTestSuite();
CLG_init();
/* Current File library needs this. */
BKE_callback_global_init();
}
static void TearDownTestSuite()
{
BKE_callback_global_finalize();
CLG_exit();
testing::Test::TearDownTestSuite();
}
@ -56,6 +62,8 @@ class AssetLibraryTestBase : public testing::Test {
void TearDown() override
{
AssetLibraryService::destroy();
if (!temp_library_path_.empty()) {
BLI_delete(temp_library_path_.c_str(), true, true);
temp_library_path_ = "";

View File

@ -0,0 +1,154 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "asset_library_service.hh"
#include "asset_library_test_common.hh"
#include "AS_asset_representation.h"
#include "AS_asset_representation.hh"
#include "DNA_asset_types.h"
#include "testing/testing.h"
namespace blender::asset_system::tests {
/** Sets up asset library loading so we have a library to load asset representations into (required
* for some functionality to perform work). */
class AssetRepresentationTest : public AssetLibraryTestBase {
public:
AssetLibrary *get_builtin_library_from_type(eAssetLibraryType type)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibraryReference ref{};
ref.type = type;
return service->get_asset_library(nullptr, ref);
}
AssetRepresentation &add_dummy_asset(AssetLibrary &library, StringRef relative_path)
{
std::unique_ptr<AssetMetaData> dummy_metadata = std::make_unique<AssetMetaData>();
return library.add_external_asset(relative_path, "Some asset name", std::move(dummy_metadata));
}
};
TEST_F(AssetRepresentationTest, weak_reference__current_file)
{
AssetLibrary *library = get_builtin_library_from_type(ASSET_LIBRARY_LOCAL);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
{
std::unique_ptr<AssetWeakReference> weak_ref = asset.make_weak_reference();
EXPECT_EQ(weak_ref->asset_library_type, ASSET_LIBRARY_LOCAL);
EXPECT_EQ(weak_ref->asset_library_identifier, nullptr);
EXPECT_STREQ(weak_ref->relative_asset_identifier, "path/to/an/asset");
}
{
/* Also test the C-API, it moves memory, so worth testing. */
AssetWeakReference *c_weak_ref = AS_asset_representation_weak_reference_create(
reinterpret_cast<::AssetRepresentation *>(&asset));
EXPECT_EQ(c_weak_ref->asset_library_type, ASSET_LIBRARY_LOCAL);
EXPECT_EQ(c_weak_ref->asset_library_identifier, nullptr);
EXPECT_STREQ(c_weak_ref->relative_asset_identifier, "path/to/an/asset");
MEM_delete(c_weak_ref);
}
}
TEST_F(AssetRepresentationTest, weak_reference__custom_library)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
{
std::unique_ptr<AssetWeakReference> weak_ref = asset.make_weak_reference();
EXPECT_EQ(weak_ref->asset_library_type, ASSET_LIBRARY_CUSTOM);
EXPECT_STREQ(weak_ref->asset_library_identifier, "My custom lib");
EXPECT_STREQ(weak_ref->relative_asset_identifier, "path/to/an/asset");
}
}
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_full_path__current_file)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *library = get_builtin_library_from_type(ASSET_LIBRARY_LOCAL);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
std::unique_ptr<AssetWeakReference> weak_ref = asset.make_weak_reference();
std::string resolved_path = service->resolve_asset_weak_reference_to_full_path(*weak_ref);
EXPECT_EQ(resolved_path, "");
}
/* #AssetLibraryService::resolve_asset_weak_reference_to_full_path(). */
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_full_path__custom_library)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
std::unique_ptr<AssetWeakReference> weak_ref = asset.make_weak_reference();
std::string expected_path = asset_library_root_ + "/" + "path/to/an/asset";
char *c_str = expected_path.data();
BLI_path_slash_native(c_str);
BLI_path_normalize(nullptr, c_str);
expected_path = c_str;
std::string resolved_path = service->resolve_asset_weak_reference_to_full_path(*weak_ref);
EXPECT_EQ(resolved_path, expected_path);
}
/* #AssetLibraryService::resolve_asset_weak_reference_to_exploded_path(). */
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_exploded_path__current_file)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *library = get_builtin_library_from_type(ASSET_LIBRARY_LOCAL);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
std::unique_ptr<AssetWeakReference> weak_ref = asset.make_weak_reference();
std::optional<AssetLibraryService::ExplodedPath> resolved_path =
service->resolve_asset_weak_reference_to_exploded_path(*weak_ref);
EXPECT_EQ(resolved_path->full_path, "path/to/an/asset");
EXPECT_EQ(resolved_path->dir_component, "");
EXPECT_EQ(resolved_path->group_component, "path");
/* ID names may contain slashes. */
EXPECT_EQ(resolved_path->name_component, "to/an/asset");
}
/* #AssetLibraryService::resolve_asset_weak_reference_to_exploded_path(). */
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_exploded_path__custom_library)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "some.blend/Material/asset/name");
std::unique_ptr<AssetWeakReference> weak_ref = asset.make_weak_reference();
std::string expected_full_path = asset_library_root_ + "/some.blend/Material/asset/name";
char *c_str = expected_full_path.data();
BLI_path_slash_native(c_str);
BLI_path_normalize(nullptr, c_str);
expected_full_path = c_str;
std::string expected_dir_path = asset_library_root_ + "/some.blend";
c_str = expected_dir_path.data();
BLI_path_slash_native(c_str);
BLI_path_normalize(nullptr, c_str);
expected_dir_path = c_str;
std::optional<AssetLibraryService::ExplodedPath> resolved_path =
service->resolve_asset_weak_reference_to_exploded_path(*weak_ref);
EXPECT_EQ(resolved_path->full_path, expected_full_path);
EXPECT_EQ(resolved_path->dir_component, expected_dir_path);
EXPECT_EQ(resolved_path->group_component, "Material");
/* ID names may contain slashes. */
EXPECT_EQ(resolved_path->name_component, "asset/name");
}
} // namespace blender::asset_system::tests

View File

@ -68,6 +68,13 @@ struct PreviewImage *BKE_asset_metadata_preview_get_from_id(const struct AssetMe
void BKE_asset_metadata_write(struct BlendWriter *writer, struct AssetMetaData *asset_data);
void BKE_asset_metadata_read(struct BlendDataReader *reader, struct AssetMetaData *asset_data);
/** Frees the weak reference and its data, and nulls the given pointer. */
void BKE_asset_weak_reference_free(AssetWeakReference **weak_ref);
AssetWeakReference *BKE_asset_weak_reference_copy(AssetWeakReference *weak_ref);
void BKE_asset_weak_reference_write(struct BlendWriter *writer,
const AssetWeakReference *weak_ref);
void BKE_asset_weak_reference_read(struct BlendDataReader *reader, AssetWeakReference *weak_ref);
#ifdef __cplusplus
}
#endif

View File

@ -72,6 +72,7 @@ set(SRC
intern/armature_selection.cc
intern/armature_update.c
intern/asset.cc
intern/asset_weak_reference.cc
intern/attribute.cc
intern/attribute_access.cc
intern/attribute_math.cc

View File

@ -0,0 +1,95 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <memory>
#include "AS_asset_identifier.hh"
#include "AS_asset_library.hh"
#include "BKE_asset.h"
#include "BLO_read_write.h"
#include "DNA_asset_types.h"
#include "MEM_guardedalloc.h"
using namespace blender;
/* #AssetWeakReference -------------------------------------------- */
AssetWeakReference::AssetWeakReference()
: asset_library_type(0), asset_library_identifier(nullptr), relative_asset_identifier(nullptr)
{
}
AssetWeakReference::AssetWeakReference(AssetWeakReference &&other)
: asset_library_type(other.asset_library_type),
asset_library_identifier(other.asset_library_identifier),
relative_asset_identifier(other.relative_asset_identifier)
{
other.asset_library_type = 0; /* Not a valid type. */
other.asset_library_identifier = nullptr;
other.relative_asset_identifier = nullptr;
}
AssetWeakReference::~AssetWeakReference()
{
MEM_delete(asset_library_identifier);
MEM_delete(relative_asset_identifier);
}
void BKE_asset_weak_reference_free(AssetWeakReference **weak_ref)
{
MEM_delete(*weak_ref);
*weak_ref = nullptr;
}
AssetWeakReference *BKE_asset_weak_reference_copy(AssetWeakReference *weak_ref)
{
if (weak_ref == nullptr) {
return nullptr;
}
AssetWeakReference *weak_ref_copy = MEM_new<AssetWeakReference>(__func__);
weak_ref_copy->asset_library_type = weak_ref->asset_library_type;
weak_ref_copy->asset_library_identifier = BLI_strdup(weak_ref->asset_library_identifier);
weak_ref_copy->relative_asset_identifier = BLI_strdup(weak_ref->relative_asset_identifier);
return weak_ref_copy;
}
std::unique_ptr<AssetWeakReference> AssetWeakReference::make_reference(
const asset_system::AssetLibrary &library,
const asset_system::AssetIdentifier &asset_identifier)
{
std::unique_ptr weak_ref = std::make_unique<AssetWeakReference>();
weak_ref->asset_library_type = library.library_type();
StringRefNull name = library.name();
if (!name.is_empty()) {
weak_ref->asset_library_identifier = BLI_strdupn(name.c_str(), name.size());
}
StringRefNull relative_identifier = asset_identifier.library_relative_identifier();
weak_ref->relative_asset_identifier = BLI_strdupn(relative_identifier.c_str(),
relative_identifier.size());
return weak_ref;
}
void BKE_asset_weak_reference_write(BlendWriter *writer, const AssetWeakReference *weak_ref)
{
BLO_write_struct(writer, AssetWeakReference, weak_ref);
BLO_write_string(writer, weak_ref->asset_library_identifier);
BLO_write_string(writer, weak_ref->relative_asset_identifier);
}
void BKE_asset_weak_reference_read(BlendDataReader *reader, AssetWeakReference *weak_ref)
{
BLO_read_data_address(reader, &weak_ref->asset_library_identifier);
BLO_read_data_address(reader, &weak_ref->relative_asset_identifier);
}

View File

@ -10,6 +10,16 @@
#include "DNA_listBase.h"
#include "DNA_uuid_types.h"
#ifdef __cplusplus
# include <memory>
namespace blender::asset_system {
class AssetLibrary;
class AssetIdentifier;
} // namespace blender::asset_system
#endif
#ifdef __cplusplus
extern "C" {
#endif
@ -134,6 +144,48 @@ typedef struct AssetLibraryReference {
int custom_library_index;
} AssetLibraryReference;
/**
* Information to refer to an asset (may be stored in files) on a "best effort" basis. It should
* work well enough for many common cases, but can break. For example when the location of the
* asset changes, the available asset libraries in the Preferences change, an asset library is
* renamed, or when a file storing this is opened on a different system (with different
* Preferences).
*
* #AssetWeakReference is similar to #AssetIdentifier, but is designed for file storage, not for
* runtime references.
*
* It has two main components:
* - A reference to the asset library: The #eAssetLibraryType and if that is not enough to identify
* the library, a library name (typically given by the user, but may change).
* - An identifier for the asset within the library: A relative path currently, which can break if
* the asset is moved. Could also be a unique key for a database for example.
*
* \note Needs freeing through the destructor, so either use a smart pointer or #MEM_delete() for
* explicit freeing.
*/
typedef struct AssetWeakReference {
char _pad[6];
short asset_library_type; /* #eAssetLibraryType */
/** If #asset_library_type is not enough to identify the asset library, this string can provide
* further location info (allocated string). Null otherwise. */
const char *asset_library_identifier;
const char *relative_asset_identifier;
#ifdef __cplusplus
AssetWeakReference();
AssetWeakReference(AssetWeakReference &&);
AssetWeakReference(const AssetWeakReference &) = delete;
/** Enables use with `std::unique_ptr<AssetWeakReference>`. */
~AssetWeakReference();
static std::unique_ptr<AssetWeakReference> make_reference(
const blender::asset_system::AssetLibrary &library,
const blender::asset_system::AssetIdentifier &asset_identifier);
#endif
} AssetWeakReference;
/**
* To be replaced by #AssetRepresentation!
*