diff options
author | Julian Eisel <julian@blender.org> | 2021-07-09 13:56:26 +0300 |
---|---|---|
committer | Sybren A. Stüvel <sybren@blender.org> | 2021-07-15 17:12:36 +0300 |
commit | a26a059244f247a85c48a28dedeb4a551110c793 (patch) | |
tree | 2a616854c0863626fef17de19b48303b7b2d35d4 /source/blender | |
parent | add6fa09249636cf5709675c9c0ef9a444bb4443 (diff) |
Assets: Initial Asset List as part of the Asset System design
Implements a basic, WIP version of the asset list. This is needed to
give the asset view UI template asset reading and displaying
functionality.
See:
* Asset System: Data Storage, Reading & UI Access - https://developer.blender.org/T88184
Especially the asset list internals should change. It uses the
File/Asset Browser's `FileList` API, which isn't really meant for access
from outside the File Browser. But as explained in T88184, it does a lot
of the stuff we currently need, so we (Sybren Stüvel and I) decided to
go this route for now. Work on a file-list rewrite which integrates well
with the asset system started in the `asset-system-filelist` branch.
Further includes:
* Operator to reload the asset list.
* New `bpy.types.AssetHandle.get_full_library_path()` function, which
gets the full path of the asset via the asset-list.
* Changes to preview loading to prevent the preview loading job to run
eternally for asset views. File Browsers have this issue too, but
should be fixed separately.
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/editors/asset/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/editors/asset/asset_edit.cc | 28 | ||||
-rw-r--r-- | source/blender/editors/asset/asset_list.cc | 637 | ||||
-rw-r--r-- | source/blender/editors/asset/asset_ops.cc | 33 | ||||
-rw-r--r-- | source/blender/editors/include/ED_asset.h | 44 | ||||
-rw-r--r-- | source/blender/editors/space_file/filelist.c | 32 | ||||
-rw-r--r-- | source/blender/editors/space_file/filelist.h | 6 | ||||
-rw-r--r-- | source/blender/editors/space_file/space_file.c | 2 | ||||
-rw-r--r-- | source/blender/editors/undo/ed_undo.c | 3 | ||||
-rw-r--r-- | source/blender/editors/util/ed_util.c | 3 | ||||
-rw-r--r-- | source/blender/editors/util/ed_util_ops.cc | 3 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_asset_types.h | 11 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_asset.c | 38 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_space.c | 1 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_workspace.c | 1 | ||||
-rw-r--r-- | source/blender/windowmanager/WM_types.h | 7 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_event_system.c | 2 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_init_exit.c | 2 |
18 files changed, 845 insertions, 10 deletions
diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index 794e64fa420..d87879bea2a 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -31,11 +31,13 @@ set(INC_SYS set(SRC asset_edit.cc + asset_list.cc asset_ops.cc ) set(LIB bf_blenloader + bf_blenkernel ) blender_add_lib(bf_editor_asset "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/asset/asset_edit.cc b/source/blender/editors/asset/asset_edit.cc index 949dc1890ad..0937af0dbf1 100644 --- a/source/blender/editors/asset/asset_edit.cc +++ b/source/blender/editors/asset/asset_edit.cc @@ -18,10 +18,15 @@ * \ingroup edasset */ +#include <memory> +#include <string> + #include "BKE_asset.h" #include "BKE_context.h" #include "BKE_lib_id.h" +#include "BLO_readfile.h" + #include "DNA_ID.h" #include "DNA_asset_types.h" #include "DNA_space_types.h" @@ -32,6 +37,8 @@ #include "ED_asset.h" +using namespace blender; + bool ED_asset_mark_id(const bContext *C, ID *id) { if (id->asset_data) { @@ -47,6 +54,9 @@ bool ED_asset_mark_id(const bContext *C, ID *id) UI_icon_render_id(C, nullptr, id, ICON_SIZE_PREVIEW, true); + /* Important for asset storage to update properly! */ + ED_assetlist_storage_tag_main_data_dirty(); + return true; } @@ -59,6 +69,9 @@ bool ED_asset_clear_id(ID *id) /* Don't clear fake user here, there's no guarantee that it was actually set by * #ED_asset_mark_id(), it might have been something/someone else. */ + /* Important for asset storage to update properly! */ + ED_assetlist_storage_tag_main_data_dirty(); + return true; } @@ -125,3 +138,18 @@ const char *ED_asset_handle_get_name(const AssetHandle *asset) { return asset->file_data->name; } + +void ED_asset_handle_get_full_library_path(const bContext *C, + const AssetLibraryReference *asset_library, + const AssetHandle *asset, + char r_full_lib_path[FILE_MAX_LIBEXTRA]) +{ + *r_full_lib_path = '\0'; + + std::string asset_path = ED_assetlist_asset_filepath_get(C, *asset_library, *asset); + if (asset_path.empty()) { + return; + } + + BLO_library_path_explode(asset_path.c_str(), r_full_lib_path, nullptr, nullptr); +} diff --git a/source/blender/editors/asset/asset_list.cc b/source/blender/editors/asset/asset_list.cc new file mode 100644 index 00000000000..1ea948d97d4 --- /dev/null +++ b/source/blender/editors/asset/asset_list.cc @@ -0,0 +1,637 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edasset + * + * Abstractions to manage runtime asset lists with a global cache for multiple UI elements to + * access. + * Internally this uses the #FileList API and structures from `filelist.c`. This is just because it + * contains most necessary logic already and there's not much time for a more long-term solution. + */ + +#include <optional> +#include <string> + +#include "BKE_asset.h" +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "BLI_function_ref.hh" +#include "BLI_hash.hh" +#include "BLI_map.hh" +#include "BLI_path_util.h" +#include "BLI_utility_mixins.hh" + +#include "DNA_asset_types.h" +#include "DNA_space_types.h" + +#include "BKE_preferences.h" + +#include "ED_asset.h" +#include "ED_fileselect.h" +#include "ED_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +/* XXX uses private header of file-space. */ +#include "../space_file/filelist.h" + +using namespace blender; + +/** + * Wrapper to add logic to the AssetLibraryReference DNA struct. + */ +class AssetLibraryReferenceWrapper { + const AssetLibraryReference reference_; + + public: + /* Intentionally not `explicit`, allow implicit conversion for convienience. Might have to be + * NOLINT */ + AssetLibraryReferenceWrapper(const AssetLibraryReference &reference); + ~AssetLibraryReferenceWrapper() = default; + + friend bool operator==(const AssetLibraryReferenceWrapper &a, + const AssetLibraryReferenceWrapper &b); + uint64_t hash() const; +}; + +AssetLibraryReferenceWrapper::AssetLibraryReferenceWrapper(const AssetLibraryReference &reference) + : reference_(reference) +{ +} + +bool operator==(const AssetLibraryReferenceWrapper &a, const AssetLibraryReferenceWrapper &b) +{ + return (a.reference_.type == b.reference_.type) && (a.reference_.type == ASSET_LIBRARY_CUSTOM) ? + (a.reference_.custom_library_index == b.reference_.custom_library_index) : + true; +} + +uint64_t AssetLibraryReferenceWrapper::hash() const +{ + uint64_t hash1 = DefaultHash<decltype(reference_.type)>{}(reference_.type); + if (reference_.type != ASSET_LIBRARY_CUSTOM) { + return hash1; + } + + uint64_t hash2 = DefaultHash<decltype(reference_.custom_library_index)>{}( + reference_.custom_library_index); + return hash1 ^ (hash2 * 33); /* Copied from DefaultHash for std::pair. */ +} + +/* -------------------------------------------------------------------- */ +/** \name Asset list API + * + * Internally re-uses #FileList from the File Browser. It does all the heavy lifting already. + * \{ */ + +/** + * RAII wrapper for `FileList` + */ +class FileListWrapper { + static void filelist_free_fn(FileList *list) + { + filelist_free(list); + MEM_freeN(list); + } + + std::unique_ptr<FileList, decltype(&filelist_free_fn)> file_list_; + + public: + explicit FileListWrapper(eFileSelectType filesel_type) + : file_list_(filelist_new(filesel_type), filelist_free_fn) + { + } + FileListWrapper(FileListWrapper &&other) = default; + FileListWrapper &operator=(FileListWrapper &&other) = default; + ~FileListWrapper() + { + /* Destructs the owned pointer. */ + file_list_ = nullptr; + } + + operator FileList *() const + { + return file_list_.get(); + } +}; + +class PreviewTimer { + /* Non-owning! The Window-Manager registers and owns this. */ + wmTimer *timer_ = nullptr; + + public: + void ensureRunning(const bContext *C) + { + if (!timer_) { + timer_ = WM_event_add_timer_notifier( + CTX_wm_manager(C), CTX_wm_window(C), NC_ASSET | ND_ASSET_LIST_PREVIEW, 0.01); + } + } + + void stop(const bContext *C) + { + if (timer_) { + WM_event_remove_timer_notifier(CTX_wm_manager(C), CTX_wm_window(C), timer_); + timer_ = nullptr; + } + } +}; + +class AssetList : NonCopyable { + FileListWrapper filelist_; + AssetLibraryReference library_ref_; + PreviewTimer previews_timer_; + + public: + AssetList() = delete; + AssetList(eFileSelectType filesel_type, const AssetLibraryReference &asset_library_ref); + AssetList(AssetList &&other) = default; + ~AssetList() = default; + + void setup(const AssetFilterSettings *filter_settings = nullptr); + void fetch(const bContext &C); + void ensurePreviewsJob(bContext *C); + void clear(bContext *C); + + bool needsRefetch() const; + void iterate(AssetListIterFn fn) const; + bool listen(const wmNotifier ¬ifier) const; + int size() const; + void tagMainDataDirty() const; + void remapID(ID *id_old, ID *id_new) const; + StringRef filepath() const; +}; + +AssetList::AssetList(eFileSelectType filesel_type, const AssetLibraryReference &asset_library_ref) + : filelist_(filesel_type), library_ref_(asset_library_ref) +{ +} + +void AssetList::setup(const AssetFilterSettings *filter_settings) +{ + FileList *files = filelist_; + + /* TODO there should only be one (FileSelectAssetLibraryUID vs. AssetLibraryReference). */ + FileSelectAssetLibraryUID file_asset_lib_ref; + file_asset_lib_ref.type = library_ref_.type; + file_asset_lib_ref.custom_library_index = library_ref_.custom_library_index; + + bUserAssetLibrary *user_library = nullptr; + + /* Ensure valid repository, or fall-back to local one. */ + if (library_ref_.type == ASSET_LIBRARY_CUSTOM) { + BLI_assert(library_ref_.custom_library_index >= 0); + + user_library = BKE_preferences_asset_library_find_from_index( + &U, library_ref_.custom_library_index); + } + + /* Relevant bits from file_refresh(). */ + /* TODO pass options properly. */ + filelist_setrecursion(files, 1); + filelist_setsorting(files, FILE_SORT_ALPHA, false); + filelist_setlibrary(files, &file_asset_lib_ref); + /* TODO different filtering settings require the list to be reread. That's a no-go for when we + * want to allow showing the same asset library with different filter settings (as in, + * different ID types). The filelist needs to be made smarter somehow, maybe goes together with + * the plan to separate the view (preview caching, filtering, etc. ) from the data. */ + filelist_setfilter_options( + files, + filter_settings != nullptr, + true, + true, /* Just always hide parent, prefer to not add an extra user option for this. */ + FILE_TYPE_BLENDERLIB, + filter_settings ? filter_settings->id_types : FILTER_ID_ALL, + true, + "", + ""); + + char path[FILE_MAXDIR] = ""; + if (user_library) { + BLI_strncpy(path, user_library->path, sizeof(path)); + filelist_setdir(files, path); + } + else { + filelist_setdir(files, path); + } +} + +void AssetList::fetch(const bContext &C) +{ + FileList *files = filelist_; + + if (filelist_needs_force_reset(files)) { + filelist_readjob_stop(files, CTX_wm_manager(&C)); + filelist_clear(files); + } + + if (filelist_needs_reading(files)) { + if (!filelist_pending(files)) { + filelist_readjob_start(files, NC_ASSET | ND_ASSET_LIST_READING, &C); + } + } + filelist_sort(files); + filelist_filter(files); +} + +bool AssetList::needsRefetch() const +{ + return filelist_needs_force_reset(filelist_) || filelist_needs_reading(filelist_); +} + +void AssetList::iterate(AssetListIterFn fn) const +{ + FileList *files = filelist_; + int numfiles = filelist_files_ensure(files); + + for (int i = 0; i < numfiles; i++) { + FileDirEntry *file = filelist_file(files, i); + if (!fn(*file)) { + break; + } + } +} + +void AssetList::ensurePreviewsJob(bContext *C) +{ + FileList *files = filelist_; + int numfiles = filelist_files_ensure(files); + + filelist_cache_previews_set(files, true); + filelist_file_cache_slidingwindow_set(files, 256); + /* TODO fetch all previews for now. */ + filelist_file_cache_block(files, numfiles / 2); + filelist_cache_previews_update(files); + + { + const bool previews_running = filelist_cache_previews_running(files) && + !filelist_cache_previews_done(files); + if (previews_running) { + previews_timer_.ensureRunning(C); + } + else { + /* Preview is not running, no need to keep generating update events! */ + previews_timer_.stop(C); + } + } +} + +void AssetList::clear(bContext *C) +{ + /* Based on #ED_fileselect_clear() */ + + FileList *files = filelist_; + filelist_readjob_stop(files, CTX_wm_manager(C)); + filelist_freelib(files); + filelist_clear(files); + + WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST, NULL); +} + +/** + * \return True if the asset-list needs a UI redraw. + */ +bool AssetList::listen(const wmNotifier ¬ifier) const +{ + switch (notifier.category) { + case NC_ID: { + if (ELEM(notifier.action, NA_RENAME)) { + return true; + } + break; + } + case NC_ASSET: + if (ELEM(notifier.data, ND_ASSET_LIST, ND_ASSET_LIST_READING, ND_ASSET_LIST_PREVIEW)) { + return true; + } + if (ELEM(notifier.action, NA_ADDED, NA_REMOVED, NA_EDITED)) { + return true; + } + break; + } + + return false; +} + +/** + * \return The number of assets in the list. + */ +int AssetList::size() const +{ + return filelist_files_ensure(filelist_); +} + +void AssetList::tagMainDataDirty() const +{ + if (filelist_needs_reset_on_main_changes(filelist_)) { + /* Full refresh of the file list if local asset data was changed. Refreshing this view + * is cheap and users expect this to be updated immediately. */ + filelist_tag_force_reset(filelist_); + } +} + +void AssetList::remapID(ID * /*id_old*/, ID * /*id_new*/) const +{ + /* Trigger full refetch of the file list if main data was changed, don't even attempt remap + * pointers. We could give file list types a id-remap callback, but it's probably not worth it. + * Refreshing local file lists is relatively cheap. */ + tagMainDataDirty(); +} + +StringRef AssetList::filepath() const +{ + return filelist_dir(filelist_); +} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Runtime asset list cache + * \{ */ + +/** + * Class managing a global asset list map, each entry being a list for a specific asset library. + */ +class AssetListStorage { + using AssetListMap = Map<AssetLibraryReferenceWrapper, AssetList>; + + public: + /* Purely static class, can't instantiate this. */ + AssetListStorage() = delete; + + static void fetch_library(const AssetLibraryReference &library_reference, + const bContext &C, + const AssetFilterSettings *filter_settings = nullptr); + static void destruct(); + static AssetList *lookup_list(const AssetLibraryReference &library_ref); + static void tagMainDataDirty(); + static void remapID(ID *id_new, ID *id_old); + + private: + static std::optional<eFileSelectType> asset_library_reference_to_fileselect_type( + const AssetLibraryReference &library_reference); + + using is_new_t = bool; + static std::tuple<AssetList &, is_new_t> ensure_list_storage( + const AssetLibraryReference &library_reference, eFileSelectType filesel_type); + + static AssetListMap &global_storage(); +}; + +void AssetListStorage::fetch_library(const AssetLibraryReference &library_reference, + const bContext &C, + const AssetFilterSettings *filter_settings) +{ + std::optional filesel_type = asset_library_reference_to_fileselect_type(library_reference); + if (!filesel_type) { + return; + } + + auto [list, is_new] = ensure_list_storage(library_reference, *filesel_type); + if (is_new || list.needsRefetch()) { + list.setup(filter_settings); + list.fetch(C); + } +} + +void AssetListStorage::destruct() +{ + global_storage().~AssetListMap(); +} + +AssetList *AssetListStorage::lookup_list(const AssetLibraryReference &library_ref) +{ + return global_storage().lookup_ptr(library_ref); +} + +void AssetListStorage::tagMainDataDirty() +{ + for (AssetList &list : global_storage().values()) { + list.tagMainDataDirty(); + } +} + +void AssetListStorage::remapID(ID *id_new, ID *id_old) +{ + for (AssetList &list : global_storage().values()) { + list.remapID(id_new, id_old); + } +} + +std::optional<eFileSelectType> AssetListStorage::asset_library_reference_to_fileselect_type( + const AssetLibraryReference &library_reference) +{ + switch (library_reference.type) { + case ASSET_LIBRARY_CUSTOM: + return FILE_LOADLIB; + case ASSET_LIBRARY_LOCAL: + return FILE_MAIN_ASSET; + } + + return std::nullopt; +} + +std::tuple<AssetList &, AssetListStorage::is_new_t> AssetListStorage::ensure_list_storage( + const AssetLibraryReference &library_reference, eFileSelectType filesel_type) +{ + AssetListMap &storage = global_storage(); + + if (AssetList *list = storage.lookup_ptr(library_reference)) { + return {*list, false}; + } + storage.add(library_reference, AssetList(filesel_type, library_reference)); + return {storage.lookup(library_reference), true}; +} + +/** + * Wrapper for Construct on First Use idiom, to avoid the Static Initialization Fiasco. + */ +AssetListStorage::AssetListMap &AssetListStorage::global_storage() +{ + static AssetListMap global_storage_; + return global_storage_; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name C-API + * \{ */ + +/** + * Invoke asset list reading, potentially in a parallel job. Won't wait until the job is done, + * and may return earlier. + */ +void ED_assetlist_storage_fetch(const AssetLibraryReference *library_reference, + const AssetFilterSettings *filter_settings, + const bContext *C) +{ + AssetListStorage::fetch_library(*library_reference, *C, filter_settings); +} + +void ED_assetlist_ensure_previews_job(const AssetLibraryReference *library_reference, bContext *C) +{ + + AssetList *list = AssetListStorage::lookup_list(*library_reference); + if (list) { + list->ensurePreviewsJob(C); + } +} + +void ED_assetlist_clear(const AssetLibraryReference *library_reference, bContext *C) +{ + AssetList *list = AssetListStorage::lookup_list(*library_reference); + if (list) { + list->clear(C); + } +} + +bool ED_assetlist_storage_has_list_for_library(const AssetLibraryReference *library_reference) +{ + return AssetListStorage::lookup_list(*library_reference) != nullptr; +} + +/* TODO expose AssetList with an iterator? */ +void ED_assetlist_iterate(const AssetLibraryReference *library_reference, AssetListIterFn fn) +{ + AssetList *list = AssetListStorage::lookup_list(*library_reference); + if (list) { + list->iterate(fn); + } +} + +/* TODO hack to use the File Browser path, so we can keep all the import logic handled by the asset + * API. Get rid of this once the File Browser is integrated better with the asset list. */ +static const char *assetlist_library_path_from_sfile_get_hack(const bContext *C) +{ + SpaceFile *sfile = CTX_wm_space_file(C); + if (!sfile || !ED_fileselect_is_asset_browser(sfile)) { + return nullptr; + } + + FileAssetSelectParams *asset_select_params = ED_fileselect_get_asset_params(sfile); + if (!asset_select_params) { + return nullptr; + } + + return filelist_dir(sfile->files); +} + +std::string ED_assetlist_asset_filepath_get(const bContext *C, + const AssetLibraryReference &library_reference, + const AssetHandle &asset_handle) +{ + if (asset_handle.file_data->id || !asset_handle.file_data->asset_data) { + return {}; + } + const char *library_path = ED_assetlist_library_path(&library_reference); + if (!library_path) { + library_path = assetlist_library_path_from_sfile_get_hack(C); + } + if (!library_path) { + return {}; + } + const char *asset_relpath = asset_handle.file_data->relpath; + + char path[FILE_MAX_LIBEXTRA]; + BLI_join_dirfile(path, sizeof(path), library_path, asset_relpath); + + return path; +} + +ID *ED_assetlist_asset_local_id_get(const AssetHandle *asset_handle) +{ + return asset_handle->file_data->asset_data ? asset_handle->file_data->id : nullptr; +} + +ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle) +{ + ImBuf *imbuf = filelist_file_getimage(asset_handle->file_data); + if (imbuf) { + return imbuf; + } + + return filelist_geticon_image_ex(asset_handle->file_data); +} + +const char *ED_assetlist_library_path(const AssetLibraryReference *library_reference) +{ + AssetList *list = AssetListStorage::lookup_list(*library_reference); + if (list) { + return list->filepath().data(); + } + return nullptr; +} + +/** + * \return True if the region needs a UI redraw. + */ +bool ED_assetlist_listen(const AssetLibraryReference *library_reference, + const wmNotifier *notifier) +{ + AssetList *list = AssetListStorage::lookup_list(*library_reference); + if (list) { + return list->listen(*notifier); + } + return false; +} + +/** + * \return The number of assets stored in the asset list for \a library_reference, or -1 if there + * is no list fetched for it. + */ +int ED_assetlist_size(const AssetLibraryReference *library_reference) +{ + AssetList *list = AssetListStorage::lookup_list(*library_reference); + if (list) { + return list->size(); + } + return -1; +} + +/** + * Tag all asset lists in the storage that show main data as needing an update (refetch). + * + * This only tags the data. If the asset list is visible on screen, the space is still responsible + * for ensuring the necessary redraw. It can use #ED_assetlist_listen() to check if the asset-list + * needs a redraw for a given notifier. + */ +void ED_assetlist_storage_tag_main_data_dirty() +{ + AssetListStorage::tagMainDataDirty(); +} + +/** + * Remapping of ID pointers within the asset lists. Typically called when an ID is deleted to clear + * all references to it (\a id_new is null then). + */ +void ED_assetlist_storage_id_remap(ID *id_old, ID *id_new) +{ + AssetListStorage::remapID(id_old, id_new); +} + +/** + * Can't wait for static deallocation to run. There's nested data allocated with our guarded + * allocator, it will complain about unfreed memory on exit. + */ +void ED_assetlist_storage_exit() +{ + AssetListStorage::destruct(); +} + +/** \} */ diff --git a/source/blender/editors/asset/asset_ops.cc b/source/blender/editors/asset/asset_ops.cc index 8ca1b488a1d..79edd1f8a6a 100644 --- a/source/blender/editors/asset/asset_ops.cc +++ b/source/blender/editors/asset/asset_ops.cc @@ -252,8 +252,41 @@ static void ASSET_OT_clear(wmOperatorType *ot) /* -------------------------------------------------------------------- */ +static bool asset_list_refresh_poll(bContext *C) +{ + const AssetLibraryReference *library = CTX_wm_asset_library(C); + if (!library) { + return false; + } + + return ED_assetlist_storage_has_list_for_library(library); +} + +static int asset_list_refresh_exec(bContext *C, wmOperator *UNUSED(unused)) +{ + const AssetLibraryReference *library = CTX_wm_asset_library(C); + ED_assetlist_clear(library, C); + return OPERATOR_FINISHED; +} + +static void ASSET_OT_list_refresh(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Refresh Asset List"; + ot->description = "Trigger a reread of the assets"; + ot->idname = "ASSET_OT_list_refresh"; + + /* api callbacks */ + ot->exec = asset_list_refresh_exec; + ot->poll = asset_list_refresh_poll; +} + +/* -------------------------------------------------------------------- */ + void ED_operatortypes_asset(void) { WM_operatortype_append(ASSET_OT_mark); WM_operatortype_append(ASSET_OT_clear); + + WM_operatortype_append(ASSET_OT_list_refresh); } diff --git a/source/blender/editors/include/ED_asset.h b/source/blender/editors/include/ED_asset.h index da0809f2e33..381071a5c6f 100644 --- a/source/blender/editors/include/ED_asset.h +++ b/source/blender/editors/include/ED_asset.h @@ -20,12 +20,18 @@ #pragma once +#include "DNA_ID_enums.h" + #ifdef __cplusplus extern "C" { #endif +struct AssetFilterSettings; struct AssetLibraryReference; struct bContext; +struct Main; +struct ReportList; +struct wmNotifier; bool ED_asset_mark_id(const struct bContext *C, struct ID *id); bool ED_asset_clear_id(struct ID *id); @@ -36,9 +42,47 @@ int ED_asset_library_reference_to_enum_value(const struct AssetLibraryReference struct AssetLibraryReference ED_asset_library_reference_from_enum_value(int value); const char *ED_asset_handle_get_name(const AssetHandle *asset); +void ED_asset_handle_get_full_library_path(const struct bContext *C, + const AssetLibraryReference *asset_library, + const AssetHandle *asset, + char r_full_lib_path[]); + +void ED_assetlist_storage_fetch(const struct AssetLibraryReference *library_reference, + const struct AssetFilterSettings *filter_settings, + const struct bContext *C); +void ED_assetlist_ensure_previews_job(const struct AssetLibraryReference *library_reference, + struct bContext *C); +void ED_assetlist_clear(const struct AssetLibraryReference *library_reference, struct bContext *C); +bool ED_assetlist_storage_has_list_for_library(const AssetLibraryReference *library_reference); +void ED_assetlist_storage_tag_main_data_dirty(void); +void ED_assetlist_storage_id_remap(struct ID *id_old, struct ID *id_new); +void ED_assetlist_storage_exit(void); + +ID *ED_assetlist_asset_local_id_get(const AssetHandle *asset_handle); +struct ImBuf *ED_assetlist_asset_image_get(const AssetHandle *asset_handle); +const char *ED_assetlist_library_path(const struct AssetLibraryReference *library_reference); + +bool ED_assetlist_listen(const struct AssetLibraryReference *library_reference, + const struct wmNotifier *notifier); +int ED_assetlist_size(const struct AssetLibraryReference *library_reference); void ED_operatortypes_asset(void); #ifdef __cplusplus } #endif + +/* TODO move to C++ asset-list header? */ +#ifdef __cplusplus + +# include <string> + +std::string ED_assetlist_asset_filepath_get(const bContext *C, + const AssetLibraryReference &library_reference, + const AssetHandle &asset_handle); + +# include "BLI_function_ref.hh" +/* Can return false to stop iterating. */ +using AssetListIterFn = blender::FunctionRef<bool(FileDirEntry &)>; +void ED_assetlist_iterate(const AssetLibraryReference *library_reference, AssetListIterFn fn); +#endif diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index c4b895dc54d..492a189fc81 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -334,6 +334,7 @@ typedef struct FileListEntryCache { /* Previews handling. */ TaskPool *previews_pool; ThreadQueue *previews_done; + size_t previews_todo_count; } FileListEntryCache; /* FileListCache.flags */ @@ -1153,7 +1154,7 @@ ImBuf *filelist_file_getimage(const FileDirEntry *file) return file->preview_icon_id ? BKE_icon_imbuf_get_buffer(file->preview_icon_id) : NULL; } -static ImBuf *filelist_geticon_image_ex(FileDirEntry *file) +ImBuf *filelist_geticon_image_ex(const FileDirEntry *file) { ImBuf *ibuf = NULL; @@ -1494,6 +1495,7 @@ static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdat /* That way task freeing function won't free th preview, since it does not own it anymore. */ atomic_cas_ptr((void **)&preview_taskdata->preview, preview, NULL); BLI_thread_queue_push(cache->previews_done, preview); + atomic_fetch_and_sub_z(&cache->previews_todo_count, 1); } // printf("%s: End (%d)...\n", __func__, threadid); @@ -1520,6 +1522,7 @@ static void filelist_cache_preview_ensure_running(FileListEntryCache *cache) if (!cache->previews_pool) { cache->previews_pool = BLI_task_pool_create_background(cache, TASK_PRIORITY_LOW); cache->previews_done = BLI_thread_queue_init(); + cache->previews_todo_count = 0; IMB_thumb_locks_acquire(); } @@ -1553,6 +1556,7 @@ static void filelist_cache_previews_free(FileListEntryCache *cache) BLI_task_pool_free(cache->previews_pool); cache->previews_pool = NULL; cache->previews_done = NULL; + cache->previews_todo_count = 0; IMB_thumb_locks_release(); } @@ -1633,6 +1637,8 @@ static void filelist_cache_init(FileListEntryCache *cache, size_t cache_size) cache->size = cache_size; cache->flags = FLC_IS_INIT; + cache->previews_todo_count = 0; + /* We cannot translate from non-main thread, so init translated strings once from here. */ IMB_thumb_ensure_translations(); } @@ -2384,7 +2390,8 @@ void filelist_cache_previews_set(FileList *filelist, const bool use_previews) if (use_previews && (filelist->flags & FL_IS_READY)) { cache->flags |= FLC_PREVIEWS_ACTIVE; - BLI_assert((cache->previews_pool == NULL) && (cache->previews_done == NULL)); + BLI_assert((cache->previews_pool == NULL) && (cache->previews_done == NULL) && + (cache->previews_todo_count == 0)); // printf("%s: Init Previews...\n", __func__); @@ -2457,6 +2464,18 @@ bool filelist_cache_previews_running(FileList *filelist) return (cache->previews_pool != NULL); } +bool filelist_cache_previews_done(FileList *filelist) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + if ((cache->flags & FLC_PREVIEWS_ACTIVE) == 0) { + /* There are no previews. */ + return false; + } + + return (cache->previews_pool == NULL) || (cache->previews_done == NULL) || + (cache->previews_todo_count == (size_t)BLI_thread_queue_len(cache->previews_done)); +} + /* would recognize .blend as well */ static bool file_is_blend_backup(const char *str) { @@ -3432,7 +3451,7 @@ static void filelist_readjob_free(void *flrjv) MEM_freeN(flrj); } -void filelist_readjob_start(FileList *filelist, const bContext *C) +void filelist_readjob_start(FileList *filelist, const int space_notifier, const bContext *C) { Main *bmain = CTX_data_main(C); wmJob *wm_job; @@ -3464,7 +3483,7 @@ void filelist_readjob_start(FileList *filelist, const bContext *C) filelist_readjob_endjob(flrj); filelist_readjob_free(flrj); - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST | NA_JOB_FINISHED, NULL); + WM_event_add_notifier(C, space_notifier | NA_JOB_FINISHED, NULL); return; } @@ -3476,10 +3495,7 @@ void filelist_readjob_start(FileList *filelist, const bContext *C) WM_JOB_PROGRESS, WM_JOB_TYPE_FILESEL_READDIR); WM_jobs_customdata_set(wm_job, flrj, filelist_readjob_free); - WM_jobs_timer(wm_job, - 0.01, - NC_SPACE | ND_SPACE_FILE_LIST, - NC_SPACE | ND_SPACE_FILE_LIST | NA_JOB_FINISHED); + WM_jobs_timer(wm_job, 0.01, space_notifier, space_notifier | NA_JOB_FINISHED); WM_jobs_callbacks( wm_job, filelist_readjob_startjob, NULL, filelist_readjob_update, filelist_readjob_endjob); diff --git a/source/blender/editors/space_file/filelist.h b/source/blender/editors/space_file/filelist.h index 0aace74e621..cb98cf6e74a 100644 --- a/source/blender/editors/space_file/filelist.h +++ b/source/blender/editors/space_file/filelist.h @@ -79,6 +79,7 @@ void filelist_init_icons(void); void filelist_free_icons(void); struct ImBuf *filelist_getimage(struct FileList *filelist, const int index); struct ImBuf *filelist_file_getimage(const FileDirEntry *file); +struct ImBuf *filelist_geticon_image_ex(const FileDirEntry *file); struct ImBuf *filelist_geticon_image(struct FileList *filelist, const int index); int filelist_geticon(struct FileList *filelist, const int index, const bool is_main); @@ -144,13 +145,16 @@ struct BlendHandle *filelist_lib(struct FileList *filelist); bool filelist_islibrary(struct FileList *filelist, char *dir, char **r_group); void filelist_freelib(struct FileList *filelist); -void filelist_readjob_start(struct FileList *filelist, const struct bContext *C); +void filelist_readjob_start(struct FileList *filelist, + int space_notifier, + const struct bContext *C); void filelist_readjob_stop(struct FileList *filelist, struct wmWindowManager *wm); int filelist_readjob_running(struct FileList *filelist, struct wmWindowManager *wm); bool filelist_cache_previews_update(struct FileList *filelist); void filelist_cache_previews_set(struct FileList *filelist, const bool use_previews); bool filelist_cache_previews_running(struct FileList *filelist); +bool filelist_cache_previews_done(struct FileList *filelist); #ifdef __cplusplus } diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index e71c00e2312..31c7dee294b 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -367,7 +367,7 @@ static void file_refresh(const bContext *C, ScrArea *area) if (filelist_needs_reading(sfile->files)) { if (!filelist_pending(sfile->files)) { - filelist_readjob_start(sfile->files, C); + filelist_readjob_start(sfile->files, NC_SPACE | ND_SPACE_FILE_LIST, C); } } diff --git a/source/blender/editors/undo/ed_undo.c b/source/blender/editors/undo/ed_undo.c index 23161028e03..0368252f54b 100644 --- a/source/blender/editors/undo/ed_undo.c +++ b/source/blender/editors/undo/ed_undo.c @@ -50,6 +50,7 @@ #include "BLO_blend_validate.h" +#include "ED_asset.h" #include "ED_gpencil.h" #include "ED_object.h" #include "ED_outliner.h" @@ -268,6 +269,8 @@ static void ed_undo_step_post(bContext *C, WM_toolsystem_refresh_active(C); WM_toolsystem_refresh_screen_all(bmain); + ED_assetlist_storage_tag_main_data_dirty(); + if (CLOG_CHECK(&LOG, 1)) { BKE_undosys_print(wm->undo_stack); } diff --git a/source/blender/editors/util/ed_util.c b/source/blender/editors/util/ed_util.c index 7bbdc58474f..73f328f85d7 100644 --- a/source/blender/editors/util/ed_util.c +++ b/source/blender/editors/util/ed_util.c @@ -46,6 +46,7 @@ #include "DEG_depsgraph.h" #include "ED_armature.h" +#include "ED_asset.h" #include "ED_image.h" #include "ED_mesh.h" #include "ED_object.h" @@ -169,6 +170,8 @@ void ED_editors_init(bContext *C) ED_space_image_paint_update(bmain, wm, scene); } + ED_assetlist_storage_tag_main_data_dirty(); + SWAP(int, reports->flag, reports_flag_prev); wm->op_undo_depth--; } diff --git a/source/blender/editors/util/ed_util_ops.cc b/source/blender/editors/util/ed_util_ops.cc index 462f7768f81..7d32d252718 100644 --- a/source/blender/editors/util/ed_util_ops.cc +++ b/source/blender/editors/util/ed_util_ops.cc @@ -36,6 +36,7 @@ #include "BLT_translation.h" +#include "ED_asset.h" #include "ED_render.h" #include "ED_undo.h" #include "ED_util.h" @@ -131,9 +132,11 @@ static int lib_id_generate_preview_exec(bContext *C, wmOperator *UNUSED(op)) if (preview) { BKE_previewimg_clear(preview); } + UI_icon_render_id(C, nullptr, id, ICON_SIZE_PREVIEW, true); WM_event_add_notifier(C, NC_ASSET | NA_EDITED, nullptr); + ED_assetlist_storage_tag_main_data_dirty(); return OPERATOR_FINISHED; } diff --git a/source/blender/makesdna/DNA_asset_types.h b/source/blender/makesdna/DNA_asset_types.h index 9b17bca2ab6..3907c158573 100644 --- a/source/blender/makesdna/DNA_asset_types.h +++ b/source/blender/makesdna/DNA_asset_types.h @@ -20,6 +20,7 @@ #pragma once +#include "DNA_defs.h" #include "DNA_listBase.h" #ifdef __cplusplus @@ -36,6 +37,16 @@ typedef struct AssetTag { char name[64]; /* MAX_NAME */ } AssetTag; +# +# +typedef struct AssetFilterSettings { + /** Tags to match against. These are newly allocated, and compared against the + * #AssetMetaData.tags. + * TODO not used and doesn't do anything yet. */ + ListBase tags; /* AssetTag */ + uint64_t id_types; /* rna_enum_id_type_filter_items */ +} AssetFilterSettings; + /** * \brief The meta-data of an asset. * By creating and giving this for a data-block (#ID.asset_data), the data-block becomes an asset. diff --git a/source/blender/makesrna/intern/rna_asset.c b/source/blender/makesrna/intern/rna_asset.c index 82430d3e174..e0879eb71f9 100644 --- a/source/blender/makesrna/intern/rna_asset.c +++ b/source/blender/makesrna/intern/rna_asset.c @@ -25,6 +25,7 @@ #include "DNA_asset_types.h" #include "DNA_defs.h" +#include "DNA_space_types.h" #include "rna_internal.h" @@ -131,6 +132,17 @@ static PointerRNA rna_AssetHandle_file_data_get(PointerRNA *ptr) return rna_pointer_inherit_refine(ptr, &RNA_FileSelectEntry, asset_handle->file_data); } +static void rna_AssetHandle_get_full_library_path( + // AssetHandle *asset, + bContext *C, + FileDirEntry *asset_file, + AssetLibraryReference *library, + char r_result[FILE_MAX_LIBEXTRA]) +{ + AssetHandle asset = {.file_data = asset_file}; + ED_asset_handle_get_full_library_path(C, library, &asset, r_result); +} + static void rna_AssetHandle_file_data_set(PointerRNA *ptr, PointerRNA value, struct ReportList *UNUSED(reports)) @@ -292,6 +304,30 @@ static void rna_def_asset_data(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Active Tag", "Index of the tag set for editing"); } +static void rna_def_asset_handle_api(StructRNA *srna) +{ + FunctionRNA *func; + PropertyRNA *parm; + + func = RNA_def_function(srna, "get_full_library_path", "rna_AssetHandle_get_full_library_path"); + RNA_def_function_flag(func, FUNC_USE_CONTEXT); + /* TODO temporarily static function, for until .py can receive the asset handle from context + * properly. `asset_file_handle` should go away too then. */ + RNA_def_function_flag(func, FUNC_NO_SELF); + parm = RNA_def_pointer(func, "asset_file_handle", "FileSelectEntry", "", ""); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_pointer(func, + "asset_library", + "AssetLibraryReference", + "", + "The asset library containing the given asset, only valid if the asset " + "library is external (i.e. not the \"Current File\" one"); + RNA_def_parameter_flags(parm, 0, PARM_REQUIRED); + parm = RNA_def_string(func, "result", NULL, FILE_MAX_LIBEXTRA, "result", ""); + RNA_def_parameter_flags(parm, PROP_THICK_WRAP, 0); + RNA_def_function_output(func, parm); +} + static void rna_def_asset_handle(BlenderRNA *brna) { StructRNA *srna; @@ -307,6 +343,8 @@ static void rna_def_asset_handle(BlenderRNA *brna) RNA_def_property_pointer_funcs( prop, "rna_AssetHandle_file_data_get", "rna_AssetHandle_file_data_set", NULL, NULL); RNA_def_property_ui_text(prop, "File Entry", "File data used to refer to the asset"); + + rna_def_asset_handle_api(srna); } static void rna_def_asset_library_reference(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index bcc6e54f4fd..966e3e34b24 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -509,6 +509,7 @@ static const EnumPropertyItem rna_enum_curve_display_handle_items[] = { #ifdef RNA_RUNTIME # include "DNA_anim_types.h" +# include "DNA_asset_types.h" # include "DNA_scene_types.h" # include "DNA_screen_types.h" # include "DNA_userdef_types.h" diff --git a/source/blender/makesrna/intern/rna_workspace.c b/source/blender/makesrna/intern/rna_workspace.c index 15bdeae16b6..b053bb0ff62 100644 --- a/source/blender/makesrna/intern/rna_workspace.c +++ b/source/blender/makesrna/intern/rna_workspace.c @@ -425,6 +425,7 @@ static void rna_def_workspace(BlenderRNA *brna) "Asset Library", "Active asset library to show in the UI, not used by the Asset Browser " "(which has its own active asset library)"); + RNA_def_property_update(prop, NC_ASSET | ND_ASSET_LIST_READING, NULL); RNA_api_workspace(srna); } diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index 0e3754ae73b..2b48a5f6648 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -439,6 +439,13 @@ typedef struct wmNotifier { #define ND_SPACE_FILE_PREVIEW (21 << 16) #define ND_SPACE_SPREADSHEET (22 << 16) +/* NC_ASSET */ +/* Denotes that the AssetList is done reading some previews. NOT that the preview generation of + * assets is done. */ +#define ND_ASSET_LIST (1 << 16) +#define ND_ASSET_LIST_PREVIEW (2 << 16) +#define ND_ASSET_LIST_READING (3 << 16) + /* subtype, 256 entries too */ #define NOTE_SUBTYPE 0x0000FF00 diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 6789a52f890..5e29a22304c 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -61,6 +61,7 @@ #include "BLT_translation.h" +#include "ED_asset.h" #include "ED_fileselect.h" #include "ED_info.h" #include "ED_screen.h" @@ -326,6 +327,7 @@ void WM_main_remap_editor_id_reference(ID *old_id, ID *new_id) } } } + ED_assetlist_storage_id_remap(old_id, new_id); wmWindowManager *wm = bmain->wm.first; if (wm && wm->message_bus) { diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index e225b9f8aca..fa087f5e226 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -109,6 +109,7 @@ #include "ED_anim_api.h" #include "ED_armature.h" +#include "ED_asset.h" #include "ED_gpencil.h" #include "ED_keyframes_edit.h" #include "ED_keyframing.h" @@ -571,6 +572,7 @@ void WM_exit_ex(bContext *C, const bool do_python) RE_engines_exit(); ED_preview_free_dbase(); /* frees a Main dbase, before BKE_blender_free! */ + ED_assetlist_storage_exit(); if (wm) { /* Before BKE_blender_free! - since the ListBases get freed there. */ |