/* SPDX-FileCopyrightText: 2023 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup bke * * Contains management of ID's for freeing & deletion. */ #include "MEM_guardedalloc.h" /* all types are needed here, in order to do memory operations */ #include "DNA_ID.h" #include "DNA_key_types.h" #include "BLI_utildefines.h" #include "BLI_listbase.h" #include "BLI_set.hh" #include "BLI_vector.hh" #include "BKE_anim_data.hh" #include "BKE_asset.hh" #include "BKE_idprop.hh" #include "BKE_idtype.hh" #include "BKE_key.hh" #include "BKE_layer.hh" #include "BKE_lib_id.hh" #include "BKE_lib_override.hh" #include "BKE_lib_remap.hh" #include "BKE_library.hh" #include "BKE_main.hh" #include "BKE_main_namemap.hh" #include "lib_intern.hh" #include "DEG_depsgraph.hh" #ifdef WITH_PYTHON # include "BPY_extern.h" #endif using namespace blender::bke::id; void BKE_libblock_free_data(ID *id, const bool do_id_user) { if (id->properties) { IDP_FreePropertyContent_ex(id->properties, do_id_user); MEM_freeN(id->properties); id->properties = nullptr; } if (id->override_library) { BKE_lib_override_library_free(&id->override_library, do_id_user); id->override_library = nullptr; } if (id->asset_data) { BKE_asset_metadata_free(&id->asset_data); } if (id->library_weak_reference != nullptr) { MEM_freeN(id->library_weak_reference); } BKE_animdata_free(id, do_id_user); } void BKE_libblock_free_datablock(ID *id, const int /*flag*/) { const IDTypeInfo *idtype_info = BKE_idtype_get_info_from_id(id); if (idtype_info != nullptr) { if (idtype_info->free_data != nullptr) { idtype_info->free_data(id); } return; } BLI_assert_msg(0, "IDType Missing IDTypeInfo"); } static int id_free(Main *bmain, void *idv, int flag, const bool use_flag_from_idtag) { ID *id = static_cast(idv); if (use_flag_from_idtag) { if ((id->tag & LIB_TAG_NO_MAIN) != 0) { flag |= LIB_ID_FREE_NO_MAIN | LIB_ID_FREE_NO_UI_USER | LIB_ID_FREE_NO_DEG_TAG; } else { flag &= ~LIB_ID_FREE_NO_MAIN; } if ((id->tag & LIB_TAG_NO_USER_REFCOUNT) != 0) { flag |= LIB_ID_FREE_NO_USER_REFCOUNT; } else { flag &= ~LIB_ID_FREE_NO_USER_REFCOUNT; } if ((id->tag & LIB_TAG_NOT_ALLOCATED) != 0) { flag |= LIB_ID_FREE_NOT_ALLOCATED; } else { flag &= ~LIB_ID_FREE_NOT_ALLOCATED; } } BLI_assert((flag & LIB_ID_FREE_NO_MAIN) != 0 || bmain != nullptr); BLI_assert((flag & LIB_ID_FREE_NO_MAIN) != 0 || (flag & LIB_ID_FREE_NOT_ALLOCATED) == 0); BLI_assert((flag & LIB_ID_FREE_NO_MAIN) != 0 || (flag & LIB_ID_FREE_NO_USER_REFCOUNT) == 0); const short type = GS(id->name); if (bmain && (flag & LIB_ID_FREE_NO_DEG_TAG) == 0) { BLI_assert(bmain->is_locked_for_linking == false); DEG_id_type_tag(bmain, type); } BKE_libblock_free_data_py(id); Key *key = ((flag & LIB_ID_FREE_NO_MAIN) == 0) ? BKE_key_from_id(id) : nullptr; if ((flag & LIB_ID_FREE_NO_USER_REFCOUNT) == 0) { BKE_libblock_relink_ex(bmain, id, nullptr, nullptr, ID_REMAP_SKIP_USER_CLEAR); } if ((flag & LIB_ID_FREE_NO_MAIN) == 0 && key != nullptr) { id_free(bmain, &key->id, flag, use_flag_from_idtag); } BKE_libblock_free_datablock(id, flag); /* avoid notifying on removed data */ if ((flag & LIB_ID_FREE_NO_MAIN) == 0) { BKE_main_lock(bmain); } if ((flag & LIB_ID_FREE_NO_UI_USER) == 0) { if (free_notifier_reference_cb) { free_notifier_reference_cb(id); } if (remap_editor_id_reference_cb) { IDRemapper remapper; remapper.add(id, nullptr); remap_editor_id_reference_cb(remapper); } } if ((flag & LIB_ID_FREE_NO_MAIN) == 0) { ListBase *lb = which_libbase(bmain, type); BLI_remlink(lb, id); if ((flag & LIB_ID_FREE_NO_NAMEMAP_REMOVE) == 0) { BKE_main_namemap_remove_name(bmain, id, id->name + 2); } } BKE_libblock_free_data(id, (flag & LIB_ID_FREE_NO_USER_REFCOUNT) == 0); if ((flag & LIB_ID_FREE_NO_MAIN) == 0) { BKE_main_unlock(bmain); } if ((flag & LIB_ID_FREE_NOT_ALLOCATED) == 0) { MEM_freeN(id); } return flag; } void BKE_id_free_ex(Main *bmain, void *idv, int flag, const bool use_flag_from_idtag) { /* ViewLayer resync needs to be delayed during Scene freeing, since internal relationships * between the Scene's master collection and its view_layers become invalid * (due to remapping). */ BKE_layer_collection_resync_forbid(); flag = id_free(bmain, idv, flag, use_flag_from_idtag); BKE_layer_collection_resync_allow(); if (bmain && (flag & LIB_ID_FREE_NO_MAIN) == 0) { BKE_main_collection_sync_remap(bmain); } } void BKE_id_free(Main *bmain, void *idv) { BKE_id_free_ex(bmain, idv, 0, true); } void BKE_id_free_us(Main *bmain, void *idv) /* test users */ { ID *id = static_cast(idv); id_us_min(id); /* XXX This is a temp (2.77) hack so that we keep same behavior as in 2.76 regarding collections * when deleting an object. Since only 'user_one' usage of objects is collections, * and only 'real user' usage of objects is scenes, removing that 'user_one' tag when there * is no more real (scene) users of an object ensures it gets fully unlinked. * But only for local objects, not linked ones! * Otherwise, there is no real way to get rid of an object anymore - * better handling of this is TODO. */ if ((GS(id->name) == ID_OB) && (id->us == 1) && !ID_IS_LINKED(id)) { id_us_clear_real(id); } if (id->us == 0) { const bool is_lib = GS(id->name) == ID_LI; BKE_libblock_unlink(bmain, id, false); BKE_id_free(bmain, id); if (is_lib) { BKE_library_main_rebuild_hierarchy(bmain); } } } static size_t id_delete(Main *bmain, blender::Set &ids_to_delete, const int extra_remapping_flags) { bool has_deleted_library = false; /* Used by batch tagged deletion, when we call BKE_id_free then, id is no more in Main database, * and has already properly unlinked its other IDs usages. * UI users are always cleared in BKE_libblock_remap_locked() call, so we can always skip it. */ const int free_flag = LIB_ID_FREE_NO_UI_USER | LIB_ID_FREE_NO_MAIN | LIB_ID_FREE_NO_USER_REFCOUNT; const int remapping_flags = (ID_REMAP_STORE_NEVER_NULL_USAGE | ID_REMAP_FORCE_NEVER_NULL_USAGE | ID_REMAP_FORCE_INTERNAL_RUNTIME_POINTERS | extra_remapping_flags); ListBase *lbarray[INDEX_ID_MAX]; const int base_count = set_listbasepointers(bmain, lbarray); BKE_main_lock(bmain); BKE_layer_collection_resync_forbid(); IDRemapper id_remapper; /* Main idea of batch deletion is to remove all IDs to be deleted from Main database. * This means that we won't have to loop over all deleted IDs to remove usages * of other deleted IDs. * This gives tremendous speed-up when deleting a large amount of IDs from a Main * containing thousands of these. * This also means that we have to be very careful here, as we by-pass many 'common' * processing, hence risking to 'corrupt' at least user counts, if not IDs themselves. */ bool keep_looping = true; while (keep_looping) { keep_looping = false; /* First tag and remove from Main all datablocks directly from target lib. * Note that we go forward here, since we want to check dependencies before users * (e.g. meshes before objects). Reduces the chances to have to loop many times in the * `while (keep_looking)` outer loop. */ for (int i = 0; i < base_count; i++) { ListBase *lb = lbarray[i]; ID *id_iter; FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id_iter) { /* NOTE: in case we delete a library, we also delete all its datablocks! */ if (ids_to_delete.contains(id_iter) || (ID_IS_LINKED(id_iter) && ids_to_delete.contains(&id_iter->lib->id))) { BLI_remlink(lb, id_iter); BKE_main_namemap_remove_name(bmain, id_iter, id_iter->name + 2); ids_to_delete.add(id_iter); id_remapper.add(id_iter, nullptr); /* Do not tag as no_main now, we want to unlink it first (lower-level ID management * code has some specific handling of 'no main' IDs that would be a problem in that * case). */ /* Forcefully also delete shapekeys of the deleted ID if any, 'orphaned' shapekeys are * not allowed in Blender and will cause lots of problem in modern code (liboverrides, * warning on write & read, etc.). */ Key *shape_key = BKE_key_from_id(id_iter); if (shape_key && !ids_to_delete.contains(&shape_key->id)) { BLI_remlink(&bmain->shapekeys, &shape_key->id); BKE_main_namemap_remove_name(bmain, &shape_key->id, shape_key->id.name + 2); ids_to_delete.add(&shape_key->id); id_remapper.add(&shape_key->id, nullptr); } keep_looping = true; } } FOREACH_MAIN_LISTBASE_ID_END; } /* Will tag 'never nullptr' users of this ID too. * * NOTE: #BKE_libblock_unlink() cannot be used here, since it would ignore indirect * links, this can lead to nasty crashing here in second, actual deleting loop. * Also, this will also flag users of deleted data that cannot be unlinked * (object using deleted obdata, etc.), so that they also get deleted. */ BKE_libblock_remap_multiple_locked(bmain, id_remapper, remapping_flags); for (ID *id_never_null_iter : id_remapper.never_null_users()) { ids_to_delete.add(id_never_null_iter); } id_remapper.clear(); } /* Since we removed IDs from Main, their own other IDs usages need to be removed 'manually'. */ blender::Vector cleanup_ids{ids_to_delete.begin(), ids_to_delete.end()}; BKE_libblock_relink_multiple( bmain, cleanup_ids, ID_REMAP_TYPE_CLEANUP, id_remapper, (ID_REMAP_FORCE_INTERNAL_RUNTIME_POINTERS | ID_REMAP_SKIP_USER_CLEAR)); cleanup_ids.clear(); /* Now we can safely mark that ID as not being in Main database anymore. */ /* NOTE: This needs to be done in a separate loop than above, otherwise some user-counts of * deleted IDs may not be properly decreased by the remappings (since `NO_MAIN` ID user-counts * is never affected). */ for (ID *id : ids_to_delete) { id->tag |= LIB_TAG_NO_MAIN; /* User-count needs to be reset artificially, since some usages may not be cleared in batch * deletion (typically, if one deleted ID uses another deleted ID, this may not be cleared by * remapping code, depending on order in which these are handled). */ id->us = ID_FAKE_USERS(id); if (!has_deleted_library && GS(id->name) == ID_LI) { has_deleted_library = true; } id_free(bmain, id, free_flag, false); } BKE_main_unlock(bmain); BKE_layer_collection_resync_allow(); BKE_main_collection_sync_remap(bmain); if (has_deleted_library) { BKE_library_main_rebuild_hierarchy(bmain); } bmain->is_memfile_undo_written = false; return size_t(ids_to_delete.size()); } void BKE_id_delete_ex(Main *bmain, void *idv, const int extra_remapping_flags) { ID *id = static_cast(idv); BLI_assert_msg((id->tag & LIB_TAG_NO_MAIN) == 0, "Cannot be used with IDs outside of Main"); blender::Set ids_to_delete = {id}; id_delete(bmain, ids_to_delete, extra_remapping_flags); } void BKE_id_delete(Main *bmain, void *idv) { BKE_id_delete_ex(bmain, idv, 0); } size_t BKE_id_multi_tagged_delete(Main *bmain) { blender::Set ids_to_delete; ID *id_iter; FOREACH_MAIN_ID_BEGIN (bmain, id_iter) { if (id_iter->tag & LIB_TAG_DOIT) { ids_to_delete.add(id_iter); } } FOREACH_MAIN_ID_END; return id_delete(bmain, ids_to_delete, 0); } size_t BKE_id_multi_delete(Main *bmain, blender::Set &ids_to_delete) { return id_delete(bmain, ids_to_delete, 0); } /* -------------------------------------------------------------------- */ /** \name Python Data Handling * \{ */ void BKE_libblock_free_data_py(ID *id) { #ifdef WITH_PYTHON # ifdef WITH_PYTHON_SAFETY BPY_id_release(id); # endif if (id->py_instance) { BPY_DECREF_RNA_INVALIDATE(id->py_instance); } #else UNUSED_VARS(id); #endif } /** \} */