Fix #104305: Crash in node editor with large asset libraries

Various UI code would store the `AssetHandle` in a way that turns out to
be unsafe. The file-data is part of the file browser caching system that
releases file-data when a certain maximum of items is in the cache. So
even while just iterating over the assets, earlier iterated asset
handles may become invalid. Now asset handles are really treated as
volatile, short lived objects.

For the asset-view, the fix was more involved. There we need an RNA
collection of asset-handles, because the UI list code requires that. So
we create a dummy collection and get the asset handles as needed by
index. This again meant that I had to keep the index of the collection
and the asset-list in sync, so all filtering had to be moved to the UI
list.
I tried duplicating the file-data out of the cache instead, but that
caused problems with managing the memory/ownership of the preview
images.

`AssetHandle` should be removed and replaced by `AssetRepresentation`,
but this would be an even more disruptive change (breaking API
compatibility too).

Fixes #104305, #105535.

Pull Request: #105773
This commit is contained in:
Julian Eisel 2023-03-16 15:40:31 +01:00
parent 55811b2919
commit a958ae36e8
24 changed files with 386 additions and 135 deletions

View File

@ -21,6 +21,8 @@ const char *AS_asset_representation_name_get(const AssetRepresentation *asset)
ATTR_WARN_UNUSED_RESULT;
AssetMetaData *AS_asset_representation_metadata_get(const AssetRepresentation *asset)
ATTR_WARN_UNUSED_RESULT;
struct ID *AS_asset_representation_local_id_get(const AssetRepresentation *asset)
ATTR_WARN_UNUSED_RESULT;
bool AS_asset_representation_is_local_id(const AssetRepresentation *asset) ATTR_WARN_UNUSED_RESULT;
bool AS_asset_representation_is_never_link(const AssetRepresentation *asset)
ATTR_WARN_UNUSED_RESULT;

View File

@ -82,6 +82,9 @@ class AssetRepresentation {
* #get_import_method(). Also returns true if there is no predefined import method
* (when #get_import_method() returns no value). */
bool may_override_import_method() const;
/** If this asset is stored inside this current file (#is_local_id() is true), this returns the
* ID's pointer, otherwise null. */
ID *local_id() const;
/** Returns if this asset is stored inside this current file, and as such fully editable. */
bool is_local_id() const;
const AssetLibrary &owner_asset_library() const;

View File

@ -97,6 +97,11 @@ bool AssetRepresentation::may_override_import_method() const
return owner_asset_library_->may_override_import_method_;
}
ID *AssetRepresentation::local_id() const
{
return is_local_id_ ? local_asset_id_ : nullptr;
}
bool AssetRepresentation::is_local_id() const
{
return is_local_id_;
@ -159,6 +164,13 @@ AssetMetaData *AS_asset_representation_metadata_get(const AssetRepresentation *a
return &asset->get_metadata();
}
ID *AS_asset_representation_local_id_get(const AssetRepresentation *asset_handle)
{
const asset_system::AssetRepresentation *asset =
reinterpret_cast<const asset_system::AssetRepresentation *>(asset_handle);
return asset->local_id();
}
bool AS_asset_representation_is_local_id(const AssetRepresentation *asset_handle)
{
const asset_system::AssetRepresentation *asset =

View File

@ -381,6 +381,8 @@ bool CTX_data_editable_gpencil_strokes(const bContext *C, ListBase *list);
const struct AssetLibraryReference *CTX_wm_asset_library_ref(const bContext *C);
struct AssetHandle CTX_wm_asset_handle(const bContext *C, bool *r_is_valid);
struct AssetRepresentation *CTX_wm_asset(const bContext *C);
bool CTX_wm_interface_locked(const bContext *C);
/**

View File

@ -1493,6 +1493,11 @@ AssetHandle CTX_wm_asset_handle(const bContext *C, bool *r_is_valid)
return AssetHandle{nullptr};
}
AssetRepresentation *CTX_wm_asset(const bContext *C)
{
return static_cast<AssetRepresentation *>(ctx_data_pointer_get(C, "asset"));
}
Depsgraph *CTX_data_depsgraph_pointer(const bContext *C)
{
Main *bmain = CTX_data_main(C);

View File

@ -24,6 +24,7 @@ set(SRC
intern/asset_catalog.cc
intern/asset_filter.cc
intern/asset_handle.cc
intern/asset_import.cc
intern/asset_indexer.cc
intern/asset_library_reference.cc
intern/asset_library_reference_enum.cc
@ -37,6 +38,7 @@ set(SRC
ED_asset_catalog.hh
ED_asset_filter.h
ED_asset_handle.h
ED_asset_import.h
ED_asset_indexer.h
ED_asset_library.h
ED_asset_list.h

View File

@ -21,6 +21,7 @@ extern "C" {
struct AssetHandle;
struct AssetRepresentation *ED_asset_handle_get_representation(const struct AssetHandle *asset);
const char *ED_asset_handle_get_name(const struct AssetHandle *asset);
struct AssetMetaData *ED_asset_handle_get_metadata(const struct AssetHandle *asset);
struct ID *ED_asset_handle_get_local_id(const struct AssetHandle *asset);
@ -45,11 +46,4 @@ void ED_asset_handle_get_full_library_path(
std::optional<eAssetImportMethod> ED_asset_handle_get_import_method(
const struct AssetHandle *asset);
namespace blender::ed::asset {
/** If the ID already exists in the database, return it, otherwise add it. */
ID *get_local_id_from_asset_or_append_and_reuse(Main &bmain, AssetHandle asset);
} // namespace blender::ed::asset
#endif

View File

@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edasset
*/
#pragma once
#include "DNA_ID_enums.h"
struct AssetRepresentation;
struct Main;
#ifdef __cplusplus
extern "C" {
#endif
struct ID *ED_asset_get_local_id_from_asset_or_append_and_reuse(
struct Main *bmain, const struct AssetRepresentation *asset_c_ptr, ID_Type idtype);
#ifdef __cplusplus
}
#endif

View File

@ -6,11 +6,12 @@
#pragma once
#include "DNA_asset_types.h"
#ifdef __cplusplus
extern "C" {
#endif
struct AssetHandle;
struct AssetLibraryReference;
struct ID;
struct bContext;
@ -49,6 +50,9 @@ void ED_assetlist_storage_id_remap(struct ID *id_old, struct ID *id_new);
*/
void ED_assetlist_storage_exit(void);
AssetHandle ED_assetlist_asset_get_by_index(const AssetLibraryReference *library_reference,
int asset_index);
struct ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle);
/**

View File

@ -30,4 +30,8 @@ blender::asset_system::AssetLibrary *ED_assetlist_library_get_once_available(
/* Can return false to stop iterating. */
using AssetListIterFn = blender::FunctionRef<bool(AssetHandle)>;
/**
* \warning Never keep the asset handle passed to \a fn outside of \a fn's scope. While iterating,
* the file data wrapped by the asset handle can be freed, since the file cache has a maximum size.
*/
void ED_assetlist_iterate(const AssetLibraryReference &library_reference, AssetListIterFn fn);

View File

@ -9,13 +9,16 @@
#include "AS_asset_representation.h"
#include "AS_asset_representation.hh"
#include "DNA_space_types.h"
#include "BLI_string.h"
#include "BLO_readfile.h"
#include "DNA_space_types.h"
#include "ED_asset_handle.h"
#include "WM_api.h"
AssetRepresentation *ED_asset_handle_get_representation(const AssetHandle *asset)
{
return asset->file_data->asset;
}
const char *ED_asset_handle_get_name(const AssetHandle *asset)
{
@ -53,36 +56,11 @@ void ED_asset_handle_get_full_library_path(const AssetHandle *asset_handle,
{
*r_full_lib_path = '\0';
std::string asset_path = AS_asset_representation_full_path_get(asset_handle->file_data->asset);
if (asset_path.empty()) {
std::string library_path = AS_asset_representation_full_library_path_get(
asset_handle->file_data->asset);
if (library_path.empty()) {
return;
}
BLO_library_path_explode(asset_path.c_str(), r_full_lib_path, nullptr, nullptr);
BLI_strncpy(r_full_lib_path, library_path.c_str(), FILE_MAX);
}
namespace blender::ed::asset {
ID *get_local_id_from_asset_or_append_and_reuse(Main &bmain, const AssetHandle asset)
{
if (ID *local_id = ED_asset_handle_get_local_id(&asset)) {
return local_id;
}
char blend_path[FILE_MAX_LIBEXTRA];
ED_asset_handle_get_full_library_path(&asset, blend_path);
const char *id_name = ED_asset_handle_get_name(&asset);
return WM_file_append_datablock(&bmain,
nullptr,
nullptr,
nullptr,
blend_path,
ED_asset_handle_get_id_type(&asset),
id_name,
BLO_LIBLINK_APPEND_RECURSIVE |
BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR |
BLO_LIBLINK_APPEND_LOCAL_ID_REUSE);
}
} // namespace blender::ed::asset

View File

@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edasset
*/
#include "AS_asset_representation.h"
#include "AS_asset_representation.hh"
#include "BLO_readfile.h"
#include "WM_api.h"
#include "ED_asset_import.h"
using namespace blender;
ID *ED_asset_get_local_id_from_asset_or_append_and_reuse(Main *bmain,
const AssetRepresentation *asset_c_ptr,
ID_Type idtype)
{
const asset_system::AssetRepresentation &asset =
*reinterpret_cast<const asset_system::AssetRepresentation *>(asset_c_ptr);
if (ID *local_id = asset.local_id()) {
return local_id;
}
std::string blend_path = asset.get_identifier().full_library_path();
if (blend_path.empty()) {
return nullptr;
}
return WM_file_append_datablock(bmain,
nullptr,
nullptr,
nullptr,
blend_path.c_str(),
idtype,
asset.get_name().c_str(),
BLO_LIBLINK_APPEND_RECURSIVE |
BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR |
BLO_LIBLINK_APPEND_LOCAL_ID_REUSE);
}

View File

@ -112,6 +112,8 @@ class AssetList : NonCopyable {
void ensurePreviewsJob(const bContext *C);
void clear(bContext *C);
AssetHandle asset_get_by_index(int index) const;
bool needsRefetch() const;
bool isLoaded() const;
asset_system::AssetLibrary *asset_library() const;
@ -139,7 +141,7 @@ void AssetList::setup()
filelist_setlibrary(files, &library_ref_);
filelist_setfilter_options(
files,
false,
true,
true,
true, /* Just always hide parent, prefer to not add an extra user option for this. */
FILE_TYPE_BLENDERLIB,
@ -246,6 +248,11 @@ void AssetList::clear(bContext *C)
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST, nullptr);
}
AssetHandle AssetList::asset_get_by_index(int index) const
{
return {filelist_file(filelist_, index)};
}
/**
* \return True if the asset-list needs a UI redraw.
*/
@ -472,6 +479,13 @@ asset_system::AssetLibrary *ED_assetlist_library_get_once_available(
return list->asset_library();
}
AssetHandle ED_assetlist_asset_get_by_index(const AssetLibraryReference *library_reference,
int asset_index)
{
const AssetList *list = AssetListStorage::lookup_list(*library_reference);
return list->asset_get_by_index(asset_index);
}
ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle)
{
ImBuf *imbuf = filelist_file_getimage(asset_handle->file_data);

View File

@ -8,9 +8,15 @@
*/
#include <new>
#include <string>
#include "AS_asset_representation.h"
#include "AS_asset_representation.hh"
#include "DNA_space_types.h"
#include "ED_asset.h"
#include "BKE_report.h"
#include "BLI_utility_mixins.hh"
@ -19,17 +25,16 @@
#include "MEM_guardedalloc.h"
#include "ED_asset_handle.h"
#include "ED_asset_temp_id_consumer.h"
using namespace blender;
class AssetTemporaryIDConsumer : NonCopyable, NonMovable {
const AssetHandle &handle_;
const AssetRepresentation *asset_;
TempLibraryContext *temp_lib_context_ = nullptr;
public:
AssetTemporaryIDConsumer(const AssetHandle &handle) : handle_(handle)
AssetTemporaryIDConsumer(const AssetRepresentation *asset) : asset_(asset)
{
}
~AssetTemporaryIDConsumer()
@ -41,20 +46,20 @@ class AssetTemporaryIDConsumer : NonCopyable, NonMovable {
ID *get_local_id()
{
return ED_asset_handle_get_local_id(&handle_);
return AS_asset_representation_local_id_get(asset_);
}
ID *import_id(ID_Type id_type, Main &bmain, ReportList &reports)
{
const char *asset_name = ED_asset_handle_get_name(&handle_);
char blend_file_path[FILE_MAX_LIBEXTRA];
ED_asset_handle_get_full_library_path(&handle_, blend_file_path);
const char *asset_name = AS_asset_representation_name_get(asset_);
std::string blend_file_path = AS_asset_representation_full_library_path_get(asset_);
temp_lib_context_ = BLO_library_temp_load_id(
&bmain, blend_file_path, id_type, asset_name, &reports);
&bmain, blend_file_path.c_str(), id_type, asset_name, &reports);
if (temp_lib_context_ == nullptr || temp_lib_context_->temp_id == nullptr) {
BKE_reportf(&reports, RPT_ERROR, "Unable to load %s from %s", asset_name, blend_file_path);
BKE_reportf(
&reports, RPT_ERROR, "Unable to load %s from %s", asset_name, blend_file_path.c_str());
return nullptr;
}
@ -70,7 +75,7 @@ AssetTempIDConsumer *ED_asset_temp_id_consumer_create(const AssetHandle *handle)
}
BLI_assert(handle->file_data->asset != nullptr);
return reinterpret_cast<AssetTempIDConsumer *>(
MEM_new<AssetTemporaryIDConsumer>(__func__, *handle));
MEM_new<AssetTemporaryIDConsumer>(__func__, ED_asset_handle_get_representation(handle)));
}
void ED_asset_temp_id_consumer_free(AssetTempIDConsumer **consumer)

View File

@ -25,6 +25,7 @@ void ED_operatortypes_asset(void);
#include "../asset/ED_asset_catalog.h"
#include "../asset/ED_asset_filter.h"
#include "../asset/ED_asset_handle.h"
#include "../asset/ED_asset_import.h"
#include "../asset/ED_asset_library.h"
#include "../asset/ED_asset_list.h"
#include "../asset/ED_asset_mark_clear.h"

View File

@ -8,6 +8,7 @@
#include <memory>
#include "BLI_function_ref.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
@ -17,8 +18,10 @@ namespace blender::nodes::geo_eval_log {
struct GeometryAttributeInfo;
}
struct PointerRNA;
struct StructRNA;
struct uiBlock;
struct uiList;
struct uiSearchItems;
namespace blender::ui {
@ -53,6 +56,60 @@ void attribute_search_add_items(StringRefNull str,
} // namespace blender::ui
enum eUIListFilterResult {
/** Never show this item, even when filter results are inverted (#UILST_FLT_EXCLUDE). */
UI_LIST_ITEM_NEVER_SHOW,
/** Show this item, unless filter results are inverted (#UILST_FLT_EXCLUDE). */
UI_LIST_ITEM_FILTER_MATCHES,
/** Don't show this item, unless filter results are inverted (#UILST_FLT_EXCLUDE). */
UI_LIST_ITEM_FILTER_MISMATCHES,
};
/**
* Function object for UI list item filtering that does the default name comparison with '*'
* wildcards. Create an instance of this once and pass it to #UI_list_filter_and_sort_items(), do
* NOT create an instance for every item, this would be costly.
*/
class uiListNameFilter {
/* Storage with an inline buffer for smaller strings (small buffer optimization). */
struct {
char filter_buff[32];
char *filter_dyn = nullptr;
} storage_;
char *filter_ = nullptr;
public:
uiListNameFilter(uiList &list);
~uiListNameFilter();
eUIListFilterResult operator()(const PointerRNA &itemptr,
blender::StringRefNull name,
int index);
};
using uiListItemFilterFn = blender::FunctionRef<eUIListFilterResult(
const PointerRNA &itemptr, blender::StringRefNull name, int index)>;
using uiListItemGetNameFn =
blender::FunctionRef<std::string(const PointerRNA &itemptr, int index)>;
/**
* Filter list items using \a item_filter_fn and sort the result. This respects the normal UI list
* filter settings like alphabetical sorting (#UILST_FLT_SORT_ALPHA), and result inverting
* (#UILST_FLT_EXCLUDE).
*
* Call this from a #uiListType::filter_items callback with any #item_filter_fn. #uiListNameFilter
* can be used to apply the default name based filtering.
*
* \param get_name_fn: In some cases the name cannot be retrieved via RNA. This function can be set
* to provide the name still.
*/
void UI_list_filter_and_sort_items(uiList *ui_list,
const struct bContext *C,
uiListItemFilterFn item_filter_fn,
PointerRNA *dataptr,
const char *propname,
uiListItemGetNameFn get_name_fn = nullptr);
/**
* Override this for all available view types.
*/

View File

@ -24,6 +24,7 @@
#include "RNA_prototypes.h"
#include "UI_interface.h"
#include "UI_interface.hh"
#include "WM_api.h"
#include "WM_types.h"
@ -32,6 +33,7 @@
struct AssetViewListData {
AssetLibraryReference asset_library_ref;
AssetFilterSettings filter_settings;
bScreen *screen;
bool show_names;
};
@ -45,8 +47,6 @@ static void asset_view_item_but_drag_set(uiBut *but, AssetHandle *asset_handle)
}
char blend_path[FILE_MAX_LIBEXTRA];
/* Context can be null here, it's only needed for a File Browser specific hack that should go
* away before too long. */
ED_asset_handle_get_full_library_path(asset_handle, blend_path);
const eAssetImportMethod import_method =
@ -68,19 +68,23 @@ static void asset_view_draw_item(uiList *ui_list,
const bContext * /*C*/,
uiLayout *layout,
PointerRNA * /*dataptr*/,
PointerRNA *itemptr,
PointerRNA * /*itemptr*/,
int /*icon*/,
PointerRNA * /*active_dataptr*/,
const char * /*active_propname*/,
int /*index*/,
int index,
int /*flt_flag*/)
{
AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata;
BLI_assert(RNA_struct_is_a(itemptr->type, &RNA_AssetHandle));
AssetHandle *asset_handle = (AssetHandle *)itemptr->data;
AssetHandle asset_handle = ED_assetlist_asset_get_by_index(&list_data->asset_library_ref, index);
uiLayoutSetContextPointer(layout, "asset_handle", itemptr);
PointerRNA file_ptr;
RNA_pointer_create(&list_data->screen->id,
&RNA_FileSelectEntry,
const_cast<FileDirEntry *>(asset_handle.file_data),
&file_ptr);
uiLayoutSetContextPointer(layout, "active_file", &file_ptr);
uiBlock *block = uiLayoutGetBlock(layout);
const bool show_names = list_data->show_names;
@ -90,8 +94,8 @@ static void asset_view_draw_item(uiList *ui_list,
uiBut *but = uiDefIconTextBut(block,
UI_BTYPE_PREVIEW_TILE,
0,
ED_asset_handle_get_preview_icon_id(asset_handle),
show_names ? ED_asset_handle_get_name(asset_handle) : "",
ED_asset_handle_get_preview_icon_id(&asset_handle),
show_names ? ED_asset_handle_get_name(&asset_handle) : "",
0,
0,
size_x,
@ -103,14 +107,43 @@ static void asset_view_draw_item(uiList *ui_list,
0,
"");
ui_def_but_icon(but,
ED_asset_handle_get_preview_icon_id(asset_handle),
ED_asset_handle_get_preview_icon_id(&asset_handle),
/* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */
UI_HAS_ICON | UI_BUT_ICON_PREVIEW);
if (!ui_list->dyn_data->custom_drag_optype) {
asset_view_item_but_drag_set(but, asset_handle);
asset_view_item_but_drag_set(but, &asset_handle);
}
}
static void asset_view_filter_items(uiList *ui_list,
const bContext *C,
PointerRNA *dataptr,
const char *propname)
{
AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata;
AssetFilterSettings &filter_settings = list_data->filter_settings;
uiListNameFilter name_filter(*ui_list);
UI_list_filter_and_sort_items(
ui_list,
C,
[&name_filter, list_data, &filter_settings](
const PointerRNA &itemptr, blender::StringRefNull name, int index) {
AssetHandle asset = ED_assetlist_asset_get_by_index(&list_data->asset_library_ref, index);
if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) {
return UI_LIST_ITEM_NEVER_SHOW;
}
return name_filter(itemptr, name, index);
},
dataptr,
propname,
[list_data](const PointerRNA & /*itemptr*/, int index) -> std::string {
AssetHandle asset = ED_assetlist_asset_get_by_index(&list_data->asset_library_ref, index);
return ED_asset_handle_get_name(&asset);
});
}
static void asset_view_listener(uiList *ui_list, wmRegionListenerParams *params)
{
AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata;
@ -136,16 +169,15 @@ uiListType *UI_UL_asset_view()
BLI_strncpy(list_type->idname, "UI_UL_asset_view", sizeof(list_type->idname));
list_type->draw_item = asset_view_draw_item;
list_type->filter_items = asset_view_filter_items;
list_type->listener = asset_view_listener;
return list_type;
}
static void asset_view_template_refresh_asset_collection(
const AssetLibraryReference &asset_library_ref,
const AssetFilterSettings &filter_settings,
PointerRNA &assets_dataptr,
const char *assets_propname)
static void populate_asset_collection(const AssetLibraryReference &asset_library_ref,
PointerRNA &assets_dataptr,
const char *assets_propname)
{
PropertyRNA *assets_prop = RNA_struct_find_property(&assets_dataptr, assets_propname);
if (!assets_prop) {
@ -164,17 +196,15 @@ static void asset_view_template_refresh_asset_collection(
RNA_property_collection_clear(&assets_dataptr, assets_prop);
ED_assetlist_iterate(asset_library_ref, [&](AssetHandle asset) {
if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) {
/* Don't do anything else, but return true to continue iterating. */
return true;
}
ED_assetlist_iterate(asset_library_ref, [&](AssetHandle /*asset*/) {
/* XXX creating a dummy #RNA_AssetHandle collection item. It's #file_data will be null. This is
* because the #FileDirEntry may be freed while iterating, there's a cache for them with a
* maximum size. Further code will query as needed it using the collection index. */
PointerRNA itemptr, fileptr;
RNA_property_collection_add(&assets_dataptr, assets_prop, &itemptr);
RNA_pointer_create(
nullptr, &RNA_FileSelectEntry, const_cast<FileDirEntry *>(asset.file_data), &fileptr);
RNA_pointer_create(nullptr, &RNA_FileSelectEntry, nullptr, &fileptr);
RNA_pointer_set(&itemptr, "file_data", fileptr);
return true;
@ -221,12 +251,12 @@ void uiTemplateAssetView(uiLayout *layout,
ED_assetlist_ensure_previews_job(&asset_library_ref, C);
const int tot_items = ED_assetlist_size(&asset_library_ref);
asset_view_template_refresh_asset_collection(
asset_library_ref, *filter_settings, *assets_dataptr, assets_propname);
populate_asset_collection(asset_library_ref, *assets_dataptr, assets_propname);
AssetViewListData *list_data = (AssetViewListData *)MEM_mallocN(sizeof(*list_data),
"AssetViewListData");
list_data->asset_library_ref = asset_library_ref;
list_data->filter_settings = *filter_settings;
list_data->screen = CTX_wm_screen(C);
list_data->show_names = (display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) == 0;

View File

@ -8,9 +8,11 @@
#include <cstring>
#include "BLI_fnmatch.h"
#include "BLI_function_ref.hh"
#include "BLI_listbase.h"
#include "BLI_math_base.h"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLI_utildefines.h"
#include "BKE_screen.h"
@ -26,12 +28,15 @@
#include "RNA_prototypes.h"
#include "UI_interface.h"
#include "UI_interface.hh"
#include "UI_view2d.h"
#include "WM_api.h"
#include "interface_intern.hh"
using namespace blender;
/**
* The validated data that was passed to #uiTemplateList (typically through Python).
* Populated through #ui_template_list_data_retrieve().
@ -148,6 +153,45 @@ static void uilist_draw_filter_default(struct uiList *ui_list,
}
}
uiListNameFilter::uiListNameFilter(uiList &list)
{
const char *filter_raw = list.filter_byname;
if (filter_raw[0]) {
const size_t slen = strlen(filter_raw);
/* Implicitly add heading/trailing wildcards if needed. */
if (slen + 3 <= sizeof(storage_.filter_buff)) {
filter_ = storage_.filter_buff;
}
else {
filter_ = storage_.filter_dyn = static_cast<char *>(
MEM_mallocN((slen + 3) * sizeof(char), "filter_dyn"));
}
BLI_strncpy_ensure_pad(filter_, filter_raw, '*', slen + 3);
}
}
uiListNameFilter::~uiListNameFilter()
{
MEM_SAFE_FREE(storage_.filter_dyn);
}
eUIListFilterResult uiListNameFilter::operator()(const PointerRNA & /* itemptr */,
StringRefNull name,
int /* index */)
{
if (!filter_) {
return UI_LIST_ITEM_FILTER_MATCHES;
}
/* Case-insensitive! */
if (fnmatch(filter_, name.c_str(), FNM_CASEFOLD) == 0) {
return UI_LIST_ITEM_FILTER_MATCHES;
}
return UI_LIST_ITEM_FILTER_MISMATCHES;
}
struct StringCmp {
char name[MAX_IDPROP_NAME];
int org_idx;
@ -159,16 +203,16 @@ static int cmpstringp(const void *p1, const void *p2)
return BLI_strcasecmp(((StringCmp *)p1)->name, ((StringCmp *)p2)->name);
}
static void uilist_filter_items_default(struct uiList *ui_list,
const struct bContext * /*C*/,
struct PointerRNA *dataptr,
const char *propname)
void UI_list_filter_and_sort_items(uiList *ui_list,
const bContext * /*C*/,
uiListItemFilterFn item_filter_fn,
PointerRNA *dataptr,
const char *propname,
uiListItemGetNameFn get_name_fn)
{
uiListDyn *dyn_data = ui_list->dyn_data;
PropertyRNA *prop = RNA_struct_find_property(dataptr, propname);
const char *filter_raw = ui_list->filter_byname;
char *filter = (char *)filter_raw, filter_buff[32], *filter_dyn = nullptr;
const bool filter_exclude = (ui_list->filter_flag & UILST_FLT_EXCLUDE) != 0;
const bool order_by_name = (ui_list->filter_sort_flag & UILST_FLT_SORT_MASK) ==
UILST_FLT_SORT_ALPHA;
@ -176,41 +220,26 @@ static void uilist_filter_items_default(struct uiList *ui_list,
dyn_data->items_shown = dyn_data->items_len = len;
if (len && (order_by_name || filter_raw[0])) {
if (len && (order_by_name || item_filter_fn)) {
StringCmp *names = nullptr;
int order_idx = 0, i = 0;
if (order_by_name) {
names = static_cast<StringCmp *>(MEM_callocN(sizeof(StringCmp) * len, "StringCmp"));
}
if (filter_raw[0]) {
const size_t slen = strlen(filter_raw);
if (item_filter_fn) {
dyn_data->items_filter_flags = static_cast<int *>(
MEM_callocN(sizeof(int) * len, "items_filter_flags"));
dyn_data->items_shown = 0;
/* Implicitly add heading/trailing wildcards if needed. */
if (slen + 3 <= sizeof(filter_buff)) {
filter = filter_buff;
}
else {
filter = filter_dyn = static_cast<char *>(
MEM_mallocN((slen + 3) * sizeof(char), "filter_dyn"));
}
BLI_strncpy_ensure_pad(filter, filter_raw, '*', slen + 3);
}
RNA_PROP_BEGIN (dataptr, itemptr, prop) {
bool do_order = false;
char *namebuf;
if (RNA_struct_is_a(itemptr.type, &RNA_AssetHandle)) {
/* XXX The AssetHandle design is hacky and meant to be temporary. It can't have a proper
* name property, so for now this hardcoded exception is needed. */
AssetHandle *asset_handle = (AssetHandle *)itemptr.data;
const char *asset_name = ED_asset_handle_get_name(asset_handle);
namebuf = BLI_strdup(asset_name);
if (get_name_fn) {
namebuf = BLI_strdup(get_name_fn(itemptr, i).c_str());
}
else {
namebuf = RNA_struct_name_get_alloc(&itemptr, nullptr, 0, nullptr);
@ -218,9 +247,13 @@ static void uilist_filter_items_default(struct uiList *ui_list,
const char *name = namebuf ? namebuf : "";
if (filter[0]) {
/* Case-insensitive! */
if (fnmatch(filter, name, FNM_CASEFOLD) == 0) {
if (item_filter_fn) {
const eUIListFilterResult filter_result = item_filter_fn(itemptr, name, i);
if (filter_result == UI_LIST_ITEM_NEVER_SHOW) {
/* Pass. */
}
else if (filter_result == UI_LIST_ITEM_FILTER_MATCHES) {
dyn_data->items_filter_flags[i] = UILST_FLT_ITEM;
if (!filter_exclude) {
dyn_data->items_shown++;
@ -266,15 +299,30 @@ static void uilist_filter_items_default(struct uiList *ui_list,
}
}
if (filter_dyn) {
MEM_freeN(filter_dyn);
}
if (names) {
MEM_freeN(names);
}
}
}
/**
* Default UI List filtering: Filter by name.
*/
static void uilist_filter_items_default(struct uiList *ui_list,
const struct bContext *C,
struct PointerRNA *dataptr,
const char *propname)
{
if (ui_list->filter_byname[0]) {
uiListNameFilter name_filter(*ui_list);
UI_list_filter_and_sort_items(ui_list, C, name_filter, dataptr, propname);
}
/* Optimization: Skip filtering entirely when there is no filter string set. */
else {
UI_list_filter_and_sort_items(ui_list, C, nullptr, dataptr, propname);
}
}
static void uilist_free_dyn_data(uiList *ui_list)
{
uiListDyn *dyn_data = ui_list->dyn_data;

View File

@ -3,6 +3,7 @@
#include "AS_asset_catalog.hh"
#include "AS_asset_catalog_tree.hh"
#include "AS_asset_library.hh"
#include "AS_asset_representation.h"
#include "BLI_multi_value_map.hh"
@ -46,7 +47,7 @@ static void node_add_menu_assets_listen_fn(const wmRegionListenerParams *params)
struct LibraryAsset {
AssetLibraryReference library_ref;
AssetHandle handle;
AssetRepresentation &asset;
};
struct AssetItemTree {
@ -93,11 +94,11 @@ static AssetItemTree build_catalog_tree(const bContext &C, const bNodeTree *node
return {};
}
ED_assetlist_iterate(all_library_ref, [&](AssetHandle asset) {
if (!ED_asset_filter_matches_asset(&type_filter, &asset)) {
ED_assetlist_iterate(all_library_ref, [&](AssetHandle asset_handle) {
if (!ED_asset_filter_matches_asset(&type_filter, &asset_handle)) {
return true;
}
const AssetMetaData &meta_data = *ED_asset_handle_get_metadata(&asset);
const AssetMetaData &meta_data = *ED_asset_handle_get_metadata(&asset_handle);
const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&meta_data, "type");
if (tree_type == nullptr || IDP_Int(tree_type) != node_tree->type) {
return true;
@ -111,7 +112,8 @@ static AssetItemTree build_catalog_tree(const bContext &C, const bNodeTree *node
if (catalog == nullptr) {
return true;
}
assets_per_path.add(catalog->path, LibraryAsset{all_library_ref, asset});
AssetRepresentation *asset = ED_asset_handle_get_representation(&asset_handle);
assets_per_path.add(catalog->path, LibraryAsset{all_library_ref, *asset});
return true;
});
@ -178,16 +180,17 @@ static void node_add_catalog_assets_draw(const bContext *C, Menu *menu)
for (const LibraryAsset &item : asset_items) {
uiLayout *col = uiLayoutColumn(layout, false);
PointerRNA file{
&screen.id, &RNA_FileSelectEntry, const_cast<FileDirEntry *>(item.handle.file_data)};
uiLayoutSetContextPointer(col, "active_file", &file);
PointerRNA asset_ptr{NULL, &RNA_AssetRepresentation, &item.asset};
uiLayoutSetContextPointer(col, "asset", &asset_ptr);
PointerRNA library_ptr{&screen.id,
&RNA_AssetLibraryReference,
const_cast<AssetLibraryReference *>(&item.library_ref)};
uiLayoutSetContextPointer(col, "asset_library_ref", &library_ptr);
uiItemO(col, ED_asset_handle_get_name(&item.handle), ICON_NONE, "NODE_OT_add_group_asset");
uiItemO(
col, AS_asset_representation_name_get(&item.asset), ICON_NONE, "NODE_OT_add_group_asset");
}
catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &child_item) {

View File

@ -39,7 +39,7 @@ struct AddNodeItem {
std::string ui_name;
std::string identifier;
std::string description;
std::optional<AssetHandle> asset;
const AssetRepresentation *asset;
std::function<void(const bContext &, bNodeTree &, bNode &)> after_add_fn;
int weight = 0;
};
@ -67,24 +67,25 @@ static void add_node_search_listen_fn(const wmRegionListenerParams *params, void
}
static void search_items_for_asset_metadata(const bNodeTree &node_tree,
const AssetHandle asset,
const AssetHandle asset_handle,
Vector<AddNodeItem> &search_items)
{
const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset);
const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset_handle);
const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&asset_data, "type");
if (tree_type == nullptr || IDP_Int(tree_type) != node_tree.type) {
return;
}
const AssetRepresentation *asset = ED_asset_handle_get_representation(&asset_handle);
AddNodeItem item{};
item.ui_name = ED_asset_handle_get_name(&asset);
item.ui_name = ED_asset_handle_get_name(&asset_handle);
item.identifier = node_tree.typeinfo->group_idname;
item.description = asset_data.description == nullptr ? "" : asset_data.description;
item.asset = asset;
item.after_add_fn = [asset](const bContext &C, bNodeTree &node_tree, bNode &node) {
Main &bmain = *CTX_data_main(&C);
node.flag &= ~NODE_OPTIONS;
node.id = asset::get_local_id_from_asset_or_append_and_reuse(bmain, asset);
node.id = ED_asset_get_local_id_from_asset_or_append_and_reuse(&bmain, asset, ID_NT);
id_us_plus(node.id);
BKE_ntree_update_tag_node_property(&node_tree, &node);
DEG_relations_tag_update(&bmain);

View File

@ -143,10 +143,10 @@ static void add_existing_group_input_fn(nodes::LinkSearchOpParams &params,
*/
static void search_link_ops_for_asset_metadata(const bNodeTree &node_tree,
const bNodeSocket &socket,
const AssetHandle asset,
const AssetHandle asset_handle,
Vector<SocketLinkOperation> &search_link_ops)
{
const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset);
const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&asset_handle);
const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&asset_data, "type");
if (tree_type == nullptr || IDP_Int(tree_type) != node_tree.type) {
return;
@ -182,7 +182,8 @@ static void search_link_ops_for_asset_metadata(const bNodeTree &node_tree,
continue;
}
const StringRef asset_name = ED_asset_handle_get_name(&asset);
AssetRepresentation *asset = ED_asset_handle_get_representation(&asset_handle);
const StringRef asset_name = ED_asset_handle_get_name(&asset_handle);
const StringRef socket_name = socket_property->name;
search_link_ops.append(
@ -193,7 +194,7 @@ static void search_link_ops_for_asset_metadata(const bNodeTree &node_tree,
bNode &node = params.add_node(params.node_tree.typeinfo->group_idname);
node.flag &= ~NODE_OPTIONS;
node.id = asset::get_local_id_from_asset_or_append_and_reuse(bmain, asset);
node.id = ED_asset_get_local_id_from_asset_or_append_and_reuse(&bmain, asset, ID_NT);
id_us_plus(node.id);
BKE_ntree_update_tag_node_property(&params.node_tree, &node);
DEG_relations_tag_update(&bmain);

View File

@ -7,6 +7,8 @@
#include <numeric>
#include "AS_asset_representation.h"
#include "MEM_guardedalloc.h"
#include "DNA_collection_types.h"
@ -378,14 +380,16 @@ void NODE_OT_add_group(wmOperatorType *ot)
/** \name Add Node Group Asset Operator
* \{ */
static bool add_node_group_asset(const bContext &C, const AssetHandle asset, ReportList &reports)
static bool add_node_group_asset(const bContext &C,
const AssetRepresentation *asset,
ReportList &reports)
{
Main &bmain = *CTX_data_main(&C);
SpaceNode &snode = *CTX_wm_space_node(&C);
bNodeTree &edit_tree = *snode.edittree;
bNodeTree *node_group = reinterpret_cast<bNodeTree *>(
asset::get_local_id_from_asset_or_append_and_reuse(bmain, asset));
ED_asset_get_local_id_from_asset_or_append_and_reuse(&bmain, asset, ID_NT));
if (!node_group) {
return false;
}
@ -427,9 +431,8 @@ static int node_add_group_asset_invoke(bContext *C, wmOperator *op, const wmEven
if (!library_ref) {
return OPERATOR_CANCELLED;
}
bool is_valid;
const AssetHandle handle = CTX_wm_asset_handle(C, &is_valid);
if (!is_valid) {
const AssetRepresentation *asset = CTX_wm_asset(C);
if (!asset) {
return OPERATOR_CANCELLED;
}
@ -442,7 +445,7 @@ static int node_add_group_asset_invoke(bContext *C, wmOperator *op, const wmEven
snode.runtime->cursor /= UI_DPI_FAC;
if (!add_node_group_asset(*C, handle, *op->reports)) {
if (!add_node_group_asset(*C, asset, *op->reports)) {
return OPERATOR_CANCELLED;
}
@ -460,12 +463,11 @@ static char *node_add_group_asset_get_description(struct bContext *C,
struct wmOperatorType * /*op*/,
struct PointerRNA * /*values*/)
{
bool is_valid;
const AssetHandle handle = CTX_wm_asset_handle(C, &is_valid);
if (!is_valid) {
const AssetRepresentation *asset = CTX_wm_asset(C);
if (!asset) {
return nullptr;
}
const AssetMetaData &asset_data = *ED_asset_handle_get_metadata(&handle);
const AssetMetaData &asset_data = *AS_asset_representation_metadata_get(asset);
if (!asset_data.description) {
return nullptr;
}

View File

@ -140,6 +140,10 @@ typedef struct AssetLibraryReference {
* Not part of the core design, we should try to get rid of it. Only needed to wrap FileDirEntry
* into a type with PropertyGroup as base, so we can have an RNA collection of #AssetHandle's to
* pass to the UI.
*
* \warning Never store this! When using #ED_assetlist_iterate(), only access it within the
* iterator function. The contained file data can be freed since the file cache has a
* maximum number of items.
*/
#
#

View File

@ -578,6 +578,17 @@ static void rna_def_asset_handle(BlenderRNA *brna)
rna_def_asset_handle_api(srna);
}
static void rna_def_asset_representation(BlenderRNA *brna)
{
StructRNA *srna;
srna = RNA_def_struct(brna, "AssetRepresentation", NULL);
RNA_def_struct_ui_text(srna,
"Asset Representation",
"Information about an entity that makes it possible for the asset system "
"to deal with the entity as asset");
}
static void rna_def_asset_catalog_path(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "AssetCatalogPath", NULL);
@ -610,6 +621,7 @@ void RNA_def_asset(BlenderRNA *brna)
rna_def_asset_data(brna);
rna_def_asset_library_reference(brna);
rna_def_asset_handle(brna);
rna_def_asset_representation(brna);
rna_def_asset_catalog_path(brna);
RNA_define_animate_sdna(true);