/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup bke */ #include #include #include #include #include #include #include #include #include "BLI_listbase.h" #include "BLI_math_base.h" #include "BLI_set.hh" #include "BLI_string.h" #include "BLI_string_utf8.h" #include "BLI_utildefines.h" #include "BKE_idprop.hh" #include "BKE_idprop_hash.hh" #include "BKE_lib_id.hh" #include "CLG_log.h" #include "MEM_guardedalloc.h" #include "BLO_read_write.hh" #include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */ namespace blender { /* IDPropertyTemplate is a union in DNA_ID.h */ /** * if the new is 'IDP_ARRAY_REALLOC_LIMIT' items less, * than #IDProperty.totallen, reallocate anyway. */ #define IDP_ARRAY_REALLOC_LIMIT 200 static CLG_LogRef LOG = {"lib.idprop"}; /** Local size table, aligned with #eIDPropertyType. */ static size_t idp_size_table[] = { 1, /* #IDP_STRING */ sizeof(int), /* #IDP_INT */ sizeof(float), /* #IDP_FLOAT */ sizeof(float[3]), /* DEPRECATED (was vector). */ sizeof(float[16]), /* DEPRECATED (was matrix). */ 0, /* #IDP_ARRAY (no fixed size). */ sizeof(ListBase), /* #IDP_GROUP */ sizeof(void *), /* #IDP_ID */ sizeof(double), /* #IDP_DOUBLE */ 0, /* #IDP_IDPARRAY (no fixed size). */ sizeof(int8_t), /* #IDP_BOOLEAN */ sizeof(int), /* #IDP_ENUM */ }; /** * Maximum amount of supported depth in IDProperties (when putting e.g. groups inside groups * inside groups etc.). * * Too many levels will lead to running out of stack memory and crashes. */ constexpr int MAX_IDPROP_DEPTH_LEVEL = 1026; /** * Write code uses one level less than runtime processing code, because it still has to write * something when it detects the issue, to ensure references to the 'limit properties' remain * valid. * Limits overly noisy continuous error messages in the console due to runtime processing and * undo/redo. */ constexpr int MAX_IDPROP_DEPTH_LEVEL_FOR_WRITE = MAX_IDPROP_DEPTH_LEVEL - 1; /** * Read code uses two level less than runtime processing code, because it still has to read * something when it detects the issue, to ensure references to the 'limit properties' remain * valid. * Limits overly noisy continuous error messages in the console due to runtime processing and * undo/redo. */ constexpr int MAX_IDPROP_DEPTH_LEVEL_FOR_READ = MAX_IDPROP_DEPTH_LEVEL - 2; static void idp_free_property_content_recurse(IDProperty *prop, const bool do_id_user, const int recursion_depth); static void idp_free_property_recurse(IDProperty *prop, const bool do_id_user, const int recursion_depth); /* -------------------------------------------------------------------- */ /** \name Array Functions (IDP Array API) * \{ */ #define GETPROP(prop, i) &(IDP_property_array_get(prop)[i]) IDProperty *IDP_NewIDPArray(const StringRef name) { IDProperty *prop = MEM_new("IDProperty prop array"); prop->type = IDP_IDPARRAY; prop->len = 0; name.copy_utf8_truncated(prop->name); return prop; } IDProperty *IDP_CopyIDPArray(const IDProperty *array, const int flag) { /* don't use MEM_dupalloc because this may be part of an array */ BLI_assert(array->type == IDP_IDPARRAY); IDProperty *narray = MEM_new(__func__, *array); narray->data.pointer = MEM_dupalloc_void(array->data.pointer); for (int i = 0; i < narray->len; i++) { /* OK, the copy functions always allocate a new structure, * which doesn't work here. instead, simply copy the * contents of the new structure into the array cell, * then free it. this makes for more maintainable * code than simply re-implementing the copy functions * in this loop. */ IDProperty *tmp = IDP_CopyProperty_ex(GETPROP(narray, i), flag); memcpy(GETPROP(narray, i), tmp, sizeof(IDProperty)); MEM_delete(tmp); } return narray; } static void IDP_FreeIDPArray(IDProperty *prop, const bool do_id_user, const int recursion_depth) { BLI_assert(prop->type == IDP_IDPARRAY); for (int i = 0; i < prop->len; i++) { idp_free_property_content_recurse(GETPROP(prop, i), do_id_user, recursion_depth + 1); } if (prop->data.pointer) { MEM_delete(static_cast(prop->data.pointer)); } } void IDP_SetIndexArray(IDProperty *prop, int index, IDProperty *item) { BLI_assert(prop->type == IDP_IDPARRAY); if (index >= prop->len || index < 0) { return; } IDProperty *old = GETPROP(prop, index); if (item != old) { idp_free_property_content_recurse(old, true, 0); memcpy(old, item, sizeof(IDProperty)); } } IDProperty *IDP_GetIndexArray(IDProperty *prop, int index) { BLI_assert(prop->type == IDP_IDPARRAY); return GETPROP(prop, index); } void IDP_AppendArray(IDProperty *prop, IDProperty *item) { BLI_assert(prop->type == IDP_IDPARRAY); IDP_ResizeIDPArray(prop, prop->len + 1); IDP_SetIndexArray(prop, prop->len - 1, item); } static void idp_group_children_map_ensure(IDProperty &prop) { BLI_assert(prop.type == IDP_GROUP); if (!prop.data.children_map) { prop.data.children_map = MEM_new(__func__); } } static int idp_resize_grow_size_calc(const int newsize) { /* NOTE: This code comes from python, here's the corresponding comment. */ /* This over-allocates proportional to the list size, making room * for additional growth. The over-allocation is mild, but is * enough to give linear-time amortized behavior over a long * sequence of appends() in the presence of a poorly-performing * system realloc(). * The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ... */ int64_t newsize_test = newsize; newsize_test = (newsize_test >> 3) + (newsize_test < 9 ? 3 : 6) + newsize_test; return int(std::min(newsize_test, std::numeric_limits::max())); } void IDP_ResizeIDPArray(IDProperty *prop, int newlen) { BLI_assert(prop->type == IDP_IDPARRAY); /* first check if the array buffer size has room */ if (newlen <= prop->totallen) { if (newlen < prop->len && prop->totallen - newlen < IDP_ARRAY_REALLOC_LIMIT) { for (int i = newlen; i < prop->len; i++) { IDP_ClearProperty(GETPROP(prop, i)); } prop->len = newlen; return; } if (newlen >= prop->len) { prop->len = newlen; return; } } /* free trailing items */ if (newlen < prop->len) { /* newlen is smaller */ for (int i = newlen; i < prop->len; i++) { IDP_ClearProperty(GETPROP(prop, i)); } } const int newsize = idp_resize_grow_size_calc(newlen); prop->data.pointer = MEM_realloc_zeroed(prop->data.pointer, sizeof(IDProperty) * size_t(newsize)); prop->len = newlen; prop->totallen = newsize; } /* ----------- Numerical Array Type ----------- */ static void idp_resize_group_array( IDProperty *prop, int newlen, void *newarr, const bool do_id_users, const int recursion_depth) { if (prop->subtype != IDP_GROUP) { return; } if (newlen >= prop->len) { /* bigger */ IDProperty **array = static_cast(newarr); for (int a = prop->len; a < newlen; a++) { array[a] = bke::idprop::create_group("IDP_ResizeArray group").release(); } } else { /* smaller */ IDProperty **array = static_cast(prop->data.pointer); for (int a = newlen; a < prop->len; a++) { idp_free_property_recurse(array[a], do_id_users, recursion_depth + 1); } } } void IDP_ResizeArray(IDProperty *prop, int newlen) { const bool is_grow = newlen >= prop->len; /* first check if the array buffer size has room */ if (newlen <= prop->totallen && prop->totallen - newlen < IDP_ARRAY_REALLOC_LIMIT) { idp_resize_group_array(prop, newlen, prop->data.pointer, true, 0); prop->len = newlen; return; } const int newsize = idp_resize_grow_size_calc(newlen); if (is_grow == false) { idp_resize_group_array(prop, newlen, prop->data.pointer, true, 0); } prop->data.pointer = MEM_realloc_zeroed(prop->data.pointer, idp_size_table[int(prop->subtype)] * size_t(newsize)); if (is_grow == true) { idp_resize_group_array(prop, newlen, prop->data.pointer, true, 0); } prop->len = newlen; prop->totallen = newsize; } static void IDP_FreeArray(IDProperty *prop, const bool do_id_user, const int recursion_depth) { if (prop->data.pointer) { idp_resize_group_array(prop, 0, nullptr, do_id_user, recursion_depth); MEM_delete_void(prop->data.pointer); } } IDPropertyUIData *IDP_ui_data_copy(const IDProperty *prop) { IDPropertyUIData *dst_ui_data = MEM_dupalloc(prop->ui_data); /* Copy extra type specific data. */ switch (IDP_ui_data_type(prop)) { case IDP_UI_DATA_TYPE_STRING: { const IDPropertyUIDataString *src = reinterpret_cast( prop->ui_data); IDPropertyUIDataString *dst = reinterpret_cast(dst_ui_data); dst->default_value = MEM_dupalloc(src->default_value); break; } case IDP_UI_DATA_TYPE_ID: { break; } case IDP_UI_DATA_TYPE_INT: { const IDPropertyUIDataInt *src = reinterpret_cast( prop->ui_data); IDPropertyUIDataInt *dst = reinterpret_cast(dst_ui_data); dst->default_array = MEM_dupalloc(src->default_array); dst->enum_items = MEM_dupalloc(src->enum_items); for (const int64_t i : IndexRange(src->enum_items_num)) { const IDPropertyUIDataEnumItem &src_item = src->enum_items[i]; IDPropertyUIDataEnumItem &dst_item = dst->enum_items[i]; dst_item.identifier = BLI_strdup(src_item.identifier); dst_item.name = BLI_strdup_null(src_item.name); dst_item.description = BLI_strdup_null(src_item.description); } break; } case IDP_UI_DATA_TYPE_BOOLEAN: { const IDPropertyUIDataBool *src = reinterpret_cast( prop->ui_data); IDPropertyUIDataBool *dst = reinterpret_cast(dst_ui_data); dst->default_array = MEM_dupalloc(src->default_array); break; } case IDP_UI_DATA_TYPE_FLOAT: { const IDPropertyUIDataFloat *src = reinterpret_cast( prop->ui_data); IDPropertyUIDataFloat *dst = reinterpret_cast(dst_ui_data); dst->default_array = MEM_dupalloc(src->default_array); break; } case IDP_UI_DATA_TYPE_UNSUPPORTED: { break; } } dst_ui_data->description = MEM_dupalloc(prop->ui_data->description); return dst_ui_data; } static IDProperty *idp_generic_copy(const IDProperty *prop, const int /*flag*/) { IDProperty *newp = MEM_new(__func__); STRNCPY(newp->name, prop->name); newp->type = prop->type; newp->flag = prop->flag; newp->data.val = prop->data.val; newp->data.val2 = prop->data.val2; if (prop->ui_data != nullptr) { newp->ui_data = IDP_ui_data_copy(prop); } return newp; } static IDProperty *IDP_CopyArray(const IDProperty *prop, const int flag) { BLI_assert(prop->type == IDP_ARRAY); IDProperty *newp = idp_generic_copy(prop, flag); if (prop->data.pointer) { newp->data.pointer = MEM_dupalloc_void(prop->data.pointer); if (prop->subtype == IDP_GROUP) { IDProperty **array = static_cast(newp->data.pointer); int a; for (a = 0; a < prop->len; a++) { array[a] = IDP_CopyProperty_ex(array[a], flag); } } } newp->len = prop->len; newp->subtype = prop->subtype; newp->totallen = prop->totallen; return newp; } /** \} */ /* -------------------------------------------------------------------- */ /** \name String Functions (IDProperty String API) * \{ */ IDProperty *IDP_NewStringMaxSize(const char *st, const size_t st_maxncpy, const StringRef name, const eIDPropertyFlag flags) { IDProperty *prop = MEM_new("IDProperty string"); if (st == nullptr) { prop->data.pointer = MEM_new_array_uninitialized(DEFAULT_ALLOC_FOR_NULL_STRINGS, "id property string 1"); *IDP_string_get(prop) = '\0'; prop->totallen = DEFAULT_ALLOC_FOR_NULL_STRINGS; prop->len = 1; /* nullptr string, has len of 1 to account for null byte. */ } else { /* Include null terminator '\0'. */ size_t stlen_bytes; if (st_maxncpy > 0) { BLI_strnlen_utf8_ex(st, st_maxncpy - 1, &stlen_bytes); } else { stlen_bytes = strlen(st); } const int stlen = int(stlen_bytes) + 1; prop->data.pointer = MEM_new_array_uninitialized(size_t(stlen), "id property string 2"); prop->len = prop->totallen = stlen; /* Ensured above, must always be true otherwise null terminator assignment will be invalid. */ BLI_assert(stlen > 0); if (stlen > 1) { memcpy(prop->data.pointer, st, size_t(stlen)); } IDP_string_get(prop)[stlen - 1] = '\0'; } prop->type = IDP_STRING; name.copy_utf8_truncated(prop->name); prop->flag = flags; return prop; } IDProperty *IDP_NewString(const char *st, const StringRef name, const eIDPropertyFlag flags) { return IDP_NewStringMaxSize(st, 0, name, flags); } IDProperty *IDP_NewString(const StringRef value, const StringRef name, const eIDPropertyFlag flags) { BLI_assert(value.size() >= 0); /* Adding one is needed for the null byte. */ return IDP_NewStringMaxSize(value.data(), size_t(value.size()) + 1, name, flags); } static IDProperty *IDP_CopyString(const IDProperty *prop, const int flag) { BLI_assert(prop->type == IDP_STRING); IDProperty *newp = idp_generic_copy(prop, flag); if (prop->data.pointer) { newp->data.pointer = MEM_dupalloc_void(prop->data.pointer); } newp->len = prop->len; newp->subtype = prop->subtype; newp->totallen = prop->totallen; return newp; } void IDP_AssignStringMaxSize(IDProperty *prop, const char *st, const size_t st_maxncpy) { /* FIXME: This function is broken for bytes (in case there are null chars in it), * needs a dedicated function which takes directly the size of the byte buffer. */ BLI_assert(prop->type == IDP_STRING); if (prop->subtype != IDP_STRING_SUB_BYTE) { /* Ensure strings remain valid UTF8. */ size_t src_len; if (st_maxncpy > 0) { BLI_strnlen_utf8_ex(st, st_maxncpy - 1, &src_len); } else { src_len = strlen(st); } const int stlen = int(src_len) + 1; IDP_ResizeArray(prop, stlen); memcpy(prop->data.pointer, st, size_t(stlen)); IDP_string_get(prop)[stlen - 1] = '\0'; return; } const bool is_byte = prop->subtype == IDP_STRING_SUB_BYTE; const int stlen = int((st_maxncpy > 0) ? BLI_strnlen(st, st_maxncpy - 1) : strlen(st)) + (is_byte ? 0 : 1); IDP_ResizeArray(prop, stlen); if (stlen > 0) { memcpy(prop->data.pointer, st, size_t(stlen)); if (is_byte == false) { IDP_string_get(prop)[stlen - 1] = '\0'; } } } void IDP_AssignString(IDProperty *prop, const char *st) { /* FIXME: Should never be called for `byte` subtype, needs an assert. */ IDP_AssignStringMaxSize(prop, st, 0); } void IDP_FreeString(IDProperty *prop) { BLI_assert(prop->type == IDP_STRING); if (prop->data.pointer) { MEM_delete(static_cast(prop->data.pointer)); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Enum Type (IDProperty Enum API) * \{ */ static void IDP_int_ui_data_free_enum_items(IDPropertyUIDataInt *ui_data) { for (const int64_t i : IndexRange(ui_data->enum_items_num)) { IDPropertyUIDataEnumItem &item = ui_data->enum_items[i]; MEM_SAFE_DELETE(item.identifier); MEM_SAFE_DELETE(item.name); MEM_SAFE_DELETE(item.description); } MEM_SAFE_DELETE(ui_data->enum_items); } const IDPropertyUIDataEnumItem *IDP_EnumItemFind(const IDProperty *prop) { BLI_assert(prop->type == IDP_INT); const IDPropertyUIDataInt *ui_data = reinterpret_cast( prop->ui_data); const int value = IDP_int_get(prop); for (const IDPropertyUIDataEnumItem &item : Span(ui_data->enum_items, ui_data->enum_items_num)) { if (item.value == value) { return &item; } } return nullptr; } bool IDP_EnumItemsValidate(const IDPropertyUIDataEnumItem *items, const int items_num, void (*error_fn)(const char *)) { Set used_values; Set used_identifiers; used_values.reserve(items_num); used_identifiers.reserve(items_num); bool is_valid = true; for (const int64_t i : IndexRange(items_num)) { const IDPropertyUIDataEnumItem &item = items[i]; if (item.identifier == nullptr || item.identifier[0] == '\0') { if (error_fn) { const std::string msg = "Item identifier is empty"; error_fn(msg.c_str()); } is_valid = false; } if (!used_identifiers.add(item.identifier)) { if (error_fn) { const std::string msg = fmt::format("Item identifier '{}' is already used", item.identifier); error_fn(msg.c_str()); } is_valid = false; } if (!used_values.add(item.value)) { if (error_fn) { const std::string msg = fmt::format( "Item value {} for item '{}' is already used", item.value, item.identifier); error_fn(msg.c_str()); } is_valid = false; } } return is_valid; } /** \} */ /* -------------------------------------------------------------------- */ /** \name ID Type (IDProperty ID API) * \{ */ static IDProperty *IDP_CopyID(const IDProperty *prop, const int flag) { BLI_assert(prop->type == IDP_ID); IDProperty *newp = idp_generic_copy(prop, flag); newp->data.pointer = prop->data.pointer; if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) { id_us_plus(IDP_ID_get(newp)); } return newp; } void IDP_AssignID(IDProperty *prop, ID *id, const int flag) { BLI_assert(prop->type == IDP_ID); /* Do not assign embedded IDs to IDProperties. */ BLI_assert(!id || (id->flag & ID_FLAG_EMBEDDED_DATA) == 0); if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0 && IDP_ID_get(prop) != nullptr) { id_us_min(IDP_ID_get(prop)); } prop->data.pointer = id; if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) { id_us_plus(IDP_ID_get(prop)); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Group Functions (IDProperty Group API) * \{ */ /** * Checks if a property with the same name as prop exists, and if so replaces it. */ static IDProperty *IDP_CopyGroup(const IDProperty *prop, const int flag) { BLI_assert(prop->type == IDP_GROUP); IDProperty *newp = idp_generic_copy(prop, flag); newp->subtype = prop->subtype; for (IDProperty &link : prop->data.group) { IDP_AddToGroup(newp, IDP_CopyProperty_ex(&link, flag)); } return newp; } void IDP_SyncGroupValues(IDProperty *dest, const IDProperty *src) { BLI_assert(dest->type == IDP_GROUP); BLI_assert(src->type == IDP_GROUP); for (IDProperty &prop : src->data.group) { IDProperty *other = IDP_GetPropertyFromGroup(dest, prop.name); if (other && prop.type == other->type) { switch (prop.type) { case IDP_INT: case IDP_FLOAT: case IDP_DOUBLE: case IDP_BOOLEAN: other->data = prop.data; break; case IDP_GROUP: IDP_SyncGroupValues(other, &prop); break; default: { IDP_ReplaceInGroup_ex(dest, IDP_CopyProperty(&prop), other, 0); break; } } } } } void IDP_SyncGroupTypes(IDProperty *dest, const IDProperty *src, const bool do_arraylen) { for (IDProperty &prop_dst : dest->data.group.items_mutable()) { const IDProperty *prop_src = IDP_GetPropertyFromGroup(const_cast(src), prop_dst.name); if (prop_src != nullptr) { /* check of we should replace? */ if ((prop_dst.type != prop_src->type || prop_dst.subtype != prop_src->subtype) || (do_arraylen && ELEM(prop_dst.type, IDP_ARRAY, IDP_IDPARRAY) && (prop_src->len != prop_dst.len))) { IDP_ReplaceInGroup_ex(dest, IDP_CopyProperty(prop_src), &prop_dst, 0); } else if (prop_dst.type == IDP_GROUP) { IDP_SyncGroupTypes(&prop_dst, prop_src, do_arraylen); } } else { IDP_FreeFromGroup(dest, &prop_dst); } } } void IDP_ReplaceGroupInGroup(IDProperty *dest, const IDProperty *src) { BLI_assert(dest->type == IDP_GROUP); BLI_assert(src->type == IDP_GROUP); for (IDProperty &prop : src->data.group) { IDProperty *old_dest_prop = IDP_GetPropertyFromGroup(dest, prop.name); IDP_ReplaceInGroup_ex(dest, IDP_CopyProperty(&prop), old_dest_prop, 0); } } void IDP_ReplaceInGroup_ex(IDProperty *group, IDProperty *prop, IDProperty *prop_exist, const int flag) { BLI_assert(group->type == IDP_GROUP); BLI_assert(prop_exist == IDP_GetPropertyFromGroup(group, prop->name)); if (prop_exist != nullptr) { /* Insert the new property at the same position as the old one in the linked list. */ BLI_insertlinkreplace(&group->data.group, prop_exist, prop); BLI_assert(group->data.children_map); group->data.children_map->children.remove_contained(prop_exist); group->data.children_map->children.add_new(prop); IDP_FreeProperty_ex(prop_exist, (flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0); } else { IDP_AddToGroup(group, prop); } } void IDP_ReplaceInGroup(IDProperty *group, IDProperty *prop) { IDProperty *prop_exist = IDP_GetPropertyFromGroup(group, prop->name); IDP_ReplaceInGroup_ex(group, prop, prop_exist, 0); } void IDP_MergeGroup_ex(IDProperty *dest, const IDProperty *src, const bool do_overwrite, const int flag) { BLI_assert(dest->type == IDP_GROUP); BLI_assert(src->type == IDP_GROUP); if (do_overwrite) { for (IDProperty &prop : src->data.group) { if (prop.type == IDP_GROUP) { IDProperty *prop_exist = IDP_GetPropertyFromGroup(dest, prop.name); if (prop_exist != nullptr) { IDP_MergeGroup_ex(prop_exist, &prop, do_overwrite, flag); continue; } } IDProperty *copy = IDP_CopyProperty_ex(&prop, flag); IDP_ReplaceInGroup_ex(dest, copy, IDP_GetPropertyFromGroup(dest, copy->name), flag); } } else { for (IDProperty &prop : src->data.group) { IDProperty *prop_exist = IDP_GetPropertyFromGroup(dest, prop.name); if (prop_exist != nullptr) { if (prop.type == IDP_GROUP) { IDP_MergeGroup_ex(prop_exist, &prop, do_overwrite, flag); continue; } } else { IDP_AddToGroup(dest, IDP_CopyProperty_ex(&prop, flag)); } } } } void IDP_MergeGroup(IDProperty *dest, const IDProperty *src, const bool do_overwrite) { IDP_MergeGroup_ex(dest, src, do_overwrite, 0); } bool IDP_AddToGroup(IDProperty *group, IDProperty *prop) { BLI_assert(group->type == IDP_GROUP); idp_group_children_map_ensure(*group); if (group->data.children_map->children.add(prop)) { group->len++; BLI_addtail(&group->data.group, prop); return true; } return false; } void IDP_RemoveFromGroup(IDProperty *group, IDProperty *prop) { BLI_assert(group->type == IDP_GROUP); BLI_assert(BLI_findindex(&group->data.group, prop) != -1); group->len--; BLI_remlink(&group->data.group, prop); BLI_assert(group->data.children_map); group->data.children_map->children.remove_contained(prop); } void IDP_FreeFromGroup(IDProperty *group, IDProperty *prop) { IDP_RemoveFromGroup(group, prop); IDP_FreeProperty(prop); } IDProperty *IDP_GetPropertyFromGroup(const IDProperty *prop, const StringRef name) { BLI_assert(prop->type == IDP_GROUP); if (prop->len == 0) { BLI_assert(prop->data.children_map == nullptr || prop->data.children_map->children.is_empty()); return nullptr; } /* If there is at least one item, the map is expected to exist. */ BLI_assert(prop->data.children_map); BLI_assert(prop->data.children_map->children.size() == prop->len); return prop->data.children_map->children.lookup_key_default_as(name, nullptr); } IDProperty *IDP_GetPropertyFromGroup_null(const IDProperty *prop, const StringRef name) { if (!prop) { return nullptr; } return IDP_GetPropertyFromGroup(prop, name); } IDProperty *IDP_GetPropertyTypeFromGroup(const IDProperty *prop, const StringRef name, const char type) { IDProperty *idprop = IDP_GetPropertyFromGroup(prop, name); return (idprop && idprop->type == type) ? idprop : nullptr; } /* Ok, the way things work, Groups free the ID Property structs of their children. * This is because all ID Property freeing functions free only direct data (not the ID Property * struct itself), but for Groups the child properties *are* considered * direct data. */ static void IDP_FreeGroup(IDProperty *prop, const bool do_id_user, const int recursion_depth) { BLI_assert(prop->type == IDP_GROUP); MEM_SAFE_DELETE(prop->data.children_map); for (IDProperty &loop : prop->data.group) { idp_free_property_content_recurse(&loop, do_id_user, recursion_depth + 1); } prop->data.group.free_no_destruct(); } std::optional IDP_group_lookup_string(const IDProperty &group, StringRef name) { const IDProperty *prop = IDP_GetPropertyFromGroup(&group, name); if (!prop || prop->type != IDP_STRING) { return std::nullopt; } return IDP_string_get(prop); } std::optional IDP_group_lookup_float(const IDProperty &group, StringRef name) { const IDProperty *prop = IDP_GetPropertyFromGroup(&group, name); if (!prop || prop->type != IDP_FLOAT) { return std::nullopt; } return IDP_float_get(prop); } std::optional IDP_group_lookup_int(const IDProperty &group, StringRef name) { const IDProperty *prop = IDP_GetPropertyFromGroup(&group, name); if (!prop || prop->type != IDP_INT) { return std::nullopt; } return IDP_int_get(prop); } std::optional IDP_group_lookup_bool(const IDProperty &group, StringRef name) { const IDProperty *prop = IDP_GetPropertyFromGroup(&group, name); if (!prop || prop->type != IDP_BOOLEAN) { return std::nullopt; } return IDP_bool_get(prop); } std::optional> IDP_group_lookup_float_array(const IDProperty &group, StringRef name, int required_size) { const IDProperty *prop = IDP_GetPropertyFromGroup(&group, name); if (!prop || prop->type != IDP_FLOAT) { return std::nullopt; } if (prop->len != required_size) { return std::nullopt; } return Span(IDP_array_float_get(prop), prop->len); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Main Functions (IDProperty Main API) * \{ */ int IDP_coerce_to_int_or_zero(const IDProperty *prop) { switch (prop->type) { case IDP_INT: return IDP_int_get(prop); case IDP_DOUBLE: return int(IDP_double_get(prop)); case IDP_FLOAT: return int(IDP_float_get(prop)); case IDP_BOOLEAN: return int(IDP_bool_get(prop)); default: return 0; } } double IDP_coerce_to_double_or_zero(const IDProperty *prop) { switch (prop->type) { case IDP_DOUBLE: return IDP_double_get(prop); case IDP_FLOAT: return double(IDP_float_get(prop)); case IDP_INT: return double(IDP_int_get(prop)); case IDP_BOOLEAN: return double(IDP_bool_get(prop)); default: return 0.0; } } float IDP_coerce_to_float_or_zero(const IDProperty *prop) { switch (prop->type) { case IDP_FLOAT: return IDP_float_get(prop); case IDP_DOUBLE: return float(IDP_double_get(prop)); case IDP_INT: return float(IDP_int_get(prop)); case IDP_BOOLEAN: return float(IDP_bool_get(prop)); default: return 0.0f; } } IDProperty *IDP_CopyProperty_ex(const IDProperty *prop, const int flag) { switch (prop->type) { case IDP_GROUP: return IDP_CopyGroup(prop, flag); case IDP_STRING: return IDP_CopyString(prop, flag); case IDP_ID: return IDP_CopyID(prop, flag); case IDP_ARRAY: return IDP_CopyArray(prop, flag); case IDP_IDPARRAY: return IDP_CopyIDPArray(prop, flag); default: return idp_generic_copy(prop, flag); } } IDProperty *IDP_CopyProperty(const IDProperty *prop) { return IDP_CopyProperty_ex(prop, 0); } void IDP_CopyPropertyContent(IDProperty *dst, const IDProperty *src) { IDProperty *idprop_tmp = IDP_CopyProperty(src); idprop_tmp->prev = dst->prev; idprop_tmp->next = dst->next; std::swap(*dst, *idprop_tmp); IDP_FreeProperty(idprop_tmp); } IDProperty *IDP_GetProperties(ID *id) { return id->properties; } IDProperty *IDP_EnsureProperties(ID *id) { if (id->properties == nullptr) { id->properties = MEM_new("IDProperty"); id->properties->type = IDP_GROUP; /* NOTE(@ideasman42): Don't overwrite the data's name and type * some functions might need this if they * don't have a real ID, should be named elsewhere. */ // STRNCPY(id->name, "top_level_group"); } return id->properties; } IDProperty *IDP_ID_system_properties_get(ID *id) { return id->system_properties; } IDProperty *IDP_ID_system_properties_ensure(ID *id) { if (id->system_properties == nullptr) { id->system_properties = MEM_new(__func__); id->system_properties->type = IDP_GROUP; /* NOTE(@ideasman42): Don't overwrite the data's name and type * some functions might need this if they * don't have a real ID, should be named elsewhere. */ // STRNCPY(id->name, "top_level_group"); } return id->system_properties; } bool IDP_EqualsProperties_ex(const IDProperty *prop1, const IDProperty *prop2, const bool is_strict) { if (prop1 == nullptr && prop2 == nullptr) { return true; } if (prop1 == nullptr || prop2 == nullptr) { return is_strict ? false : true; } if (prop1->type != prop2->type) { return false; } switch (prop1->type) { case IDP_INT: return (IDP_int_get(prop1) == IDP_int_get(prop2)); case IDP_FLOAT: #if !defined(NDEBUG) && defined(WITH_PYTHON) { float p1 = IDP_float_get(prop1); float p2 = IDP_float_get(prop2); if ((p1 != p2) && ((fabsf(p1 - p2) / max_ff(fabsf(p1), fabsf(p2))) < 0.001f)) { printf( "WARNING: Comparing two float properties that have nearly the same value (%f vs. " "%f)\n", p1, p2); printf(" p1: "); IDP_print(prop1); printf(" p2: "); IDP_print(prop2); } } #endif return (IDP_float_get(prop1) == IDP_float_get(prop2)); case IDP_DOUBLE: return (IDP_double_get(prop1) == IDP_double_get(prop2)); case IDP_BOOLEAN: return (IDP_bool_get(prop1) == IDP_bool_get(prop2)); case IDP_STRING: { return ((prop1->len == prop2->len) && STREQLEN(IDP_string_get(prop1), IDP_string_get(prop2), size_t(prop1->len))); } case IDP_ARRAY: if (prop1->len == prop2->len && prop1->subtype == prop2->subtype) { return (memcmp(IDP_array_voidp_get(prop1), IDP_array_voidp_get(prop2), idp_size_table[int(prop1->subtype)] * size_t(prop1->len)) == 0); } return false; case IDP_GROUP: { if (is_strict && prop1->len != prop2->len) { return false; } for (const IDProperty &link1 : prop1->data.group) { const IDProperty *link2 = IDP_GetPropertyFromGroup(prop2, link1.name); if (!IDP_EqualsProperties_ex(&link1, link2, is_strict)) { return false; } } return true; } case IDP_IDPARRAY: { const IDProperty *array1 = IDP_property_array_get(prop1); const IDProperty *array2 = IDP_property_array_get(prop2); if (prop1->len != prop2->len) { return false; } for (int i = 0; i < prop1->len; i++) { if (!IDP_EqualsProperties_ex(&array1[i], &array2[i], is_strict)) { return false; } } return true; } case IDP_ID: return (IDP_ID_get(prop1) == IDP_ID_get(prop2)); default: BLI_assert_unreachable(); break; } return true; } bool IDP_EqualsProperties(const IDProperty *prop1, const IDProperty *prop2) { return IDP_EqualsProperties_ex(prop1, prop2, true); } IDProperty *IDP_New(const char type, const IDPropertyTemplate *val, const StringRef name, const eIDPropertyFlag flags) { IDProperty *prop = nullptr; switch (type) { case IDP_INT: prop = MEM_new("IDProperty int"); prop->data.val = val->i; break; case IDP_FLOAT: prop = MEM_new("IDProperty float"); *reinterpret_cast(&prop->data.val) = val->f; break; case IDP_DOUBLE: prop = MEM_new("IDProperty double"); *reinterpret_cast(&prop->data.val) = val->d; break; case IDP_BOOLEAN: prop = MEM_new("IDProperty boolean"); prop->data.val = bool(val->i); break; case IDP_ARRAY: { /* FIXME: This seems to be the only place in code allowing `IDP_GROUP` as subtype of an * `IDP_ARRAY`. This is most likely a mistake. `IDP_GROUP` array should be of type * `IDP_IDPARRAY`, as done e.g. in #idp_from_PySequence_Buffer in bpy API. */ if (ELEM(val->array.type, IDP_FLOAT, IDP_INT, IDP_DOUBLE, IDP_GROUP, IDP_BOOLEAN)) { prop = MEM_new("IDProperty array"); prop->subtype = val->array.type; if (val->array.len) { prop->data.pointer = MEM_new_array_zeroed( size_t(val->array.len), idp_size_table[val->array.type], "id property array"); } prop->len = prop->totallen = val->array.len; break; } CLOG_ERROR(&LOG, "bad array type."); return nullptr; } case IDP_STRING: { const char *st = val->string.str; prop = MEM_new("IDProperty string"); if (val->string.subtype == IDP_STRING_SUB_BYTE) { /* NOTE: Intentionally not null terminated. */ if (st == nullptr) { prop->data.pointer = MEM_new_array_uninitialized(DEFAULT_ALLOC_FOR_NULL_STRINGS, "id property string 1"); *IDP_string_get(prop) = '\0'; prop->totallen = DEFAULT_ALLOC_FOR_NULL_STRINGS; prop->len = 0; } else { prop->data.pointer = MEM_new_array_uninitialized(size_t(val->string.len), "id property string 2"); prop->len = prop->totallen = val->string.len; memcpy(prop->data.pointer, st, size_t(val->string.len)); } prop->subtype = IDP_STRING_SUB_BYTE; } else { if (st == nullptr || val->string.len <= 1) { prop->data.pointer = MEM_new_array_uninitialized(DEFAULT_ALLOC_FOR_NULL_STRINGS, "id property string 1"); *IDP_string_get(prop) = '\0'; prop->totallen = DEFAULT_ALLOC_FOR_NULL_STRINGS; /* nullptr string, has len of 1 to account for null byte. */ prop->len = 1; } else { BLI_assert(int(val->string.len) <= int(strlen(st)) + 1); prop->data.pointer = MEM_new_array_uninitialized(size_t(val->string.len), "id property string 3"); memcpy(prop->data.pointer, st, size_t(val->string.len) - 1); IDP_string_get(prop)[val->string.len - 1] = '\0'; prop->len = prop->totallen = val->string.len; } prop->subtype = IDP_STRING_SUB_UTF8; } break; } case IDP_GROUP: { /* Values are set properly by calloc. */ prop = MEM_new("IDProperty group"); break; } case IDP_ID: { prop = MEM_new("IDProperty datablock"); prop->data.pointer = static_cast(val->id); prop->type = IDP_ID; id_us_plus(IDP_ID_get(prop)); break; } default: { prop = MEM_new("IDProperty array"); break; } } prop->type = eIDPropertyType(type); name.copy_utf8_truncated(prop->name); prop->flag = flags; return prop; } IDProperty *IDP_NewInt(const int value, const blender::StringRef name, const eIDPropertyFlag flags) { IDPropertyTemplate prop_template{0}; prop_template.i = value; return IDP_New(IDP_INT, &prop_template, name, flags); } void IDP_ui_data_free_unique_contents(IDPropertyUIData *ui_data, const eIDPropertyUIDataType type, const IDPropertyUIData *other) { if (ui_data->description != other->description) { MEM_SAFE_DELETE(ui_data->description); } switch (type) { case IDP_UI_DATA_TYPE_STRING: { const IDPropertyUIDataString *other_string = reinterpret_cast(other); IDPropertyUIDataString *ui_data_string = reinterpret_cast(ui_data); if (ui_data_string->default_value != other_string->default_value) { MEM_SAFE_DELETE(ui_data_string->default_value); } break; } case IDP_UI_DATA_TYPE_ID: { break; } case IDP_UI_DATA_TYPE_INT: { const IDPropertyUIDataInt *other_int = reinterpret_cast(other); IDPropertyUIDataInt *ui_data_int = reinterpret_cast(ui_data); if (ui_data_int->default_array != other_int->default_array) { MEM_SAFE_DELETE(ui_data_int->default_array); } if (ui_data_int->enum_items != other_int->enum_items) { IDP_int_ui_data_free_enum_items(ui_data_int); } break; } case IDP_UI_DATA_TYPE_BOOLEAN: { const IDPropertyUIDataBool *other_bool = reinterpret_cast( other); IDPropertyUIDataBool *ui_data_bool = reinterpret_cast(ui_data); if (ui_data_bool->default_array != other_bool->default_array) { MEM_SAFE_DELETE(ui_data_bool->default_array); } break; } case IDP_UI_DATA_TYPE_FLOAT: { const IDPropertyUIDataFloat *other_float = reinterpret_cast( other); IDPropertyUIDataFloat *ui_data_float = reinterpret_cast(ui_data); if (ui_data_float->default_array != other_float->default_array) { MEM_SAFE_DELETE(ui_data_float->default_array); } break; } case IDP_UI_DATA_TYPE_UNSUPPORTED: { break; } } } static void ui_data_free(IDPropertyUIData *ui_data, const eIDPropertyUIDataType type) { switch (type) { case IDP_UI_DATA_TYPE_STRING: { IDPropertyUIDataString *ui_data_string = reinterpret_cast(ui_data); MEM_SAFE_DELETE(ui_data_string->default_value); break; } case IDP_UI_DATA_TYPE_ID: { break; } case IDP_UI_DATA_TYPE_INT: { IDPropertyUIDataInt *ui_data_int = reinterpret_cast(ui_data); MEM_SAFE_DELETE(ui_data_int->default_array); IDP_int_ui_data_free_enum_items(ui_data_int); break; } case IDP_UI_DATA_TYPE_BOOLEAN: { IDPropertyUIDataBool *ui_data_bool = reinterpret_cast(ui_data); MEM_SAFE_DELETE(ui_data_bool->default_array); break; } case IDP_UI_DATA_TYPE_FLOAT: { IDPropertyUIDataFloat *ui_data_float = reinterpret_cast(ui_data); MEM_SAFE_DELETE(ui_data_float->default_array); break; } case IDP_UI_DATA_TYPE_UNSUPPORTED: { break; } } MEM_SAFE_DELETE(ui_data->description); MEM_delete(ui_data); } void IDP_ui_data_free(IDProperty *prop) { ui_data_free(prop->ui_data, IDP_ui_data_type(prop)); prop->ui_data = nullptr; } static void idp_free_property_content_recurse(IDProperty *prop, const bool do_id_user, const int recursion_depth) { if (recursion_depth > MAX_IDPROP_DEPTH_LEVEL) { CLOG_ERROR(&LOG, "Too deep level of IDProperties embedding detected (over %d levels), this is " "likely caused by a buggy script or add-on. The data in property '%s' will not " "be freed", MAX_IDPROP_DEPTH_LEVEL, prop->name); return; } switch (prop->type) { case IDP_INT: case IDP_FLOAT: case IDP_DOUBLE: case IDP_BOOLEAN: break; case IDP_ARRAY: IDP_FreeArray(prop, do_id_user, recursion_depth); break; case IDP_STRING: IDP_FreeString(prop); break; case IDP_GROUP: IDP_FreeGroup(prop, do_id_user, recursion_depth); break; case IDP_IDPARRAY: IDP_FreeIDPArray(prop, do_id_user, recursion_depth); break; case IDP_ID: if (do_id_user) { id_us_min(IDP_ID_get(prop)); } break; } if (prop->ui_data != nullptr) { IDP_ui_data_free(prop); } } static void idp_free_property_recurse(IDProperty *prop, const bool do_id_user, const int recursion_depth) { idp_free_property_content_recurse(prop, do_id_user, recursion_depth); MEM_delete(prop); } void IDP_FreeProperty_ex(IDProperty *prop, const bool do_id_user) { idp_free_property_recurse(prop, do_id_user, 0); } void IDP_FreeProperty(IDProperty *prop) { idp_free_property_recurse(prop, true, 0); } void IDP_ClearProperty(IDProperty *prop) { idp_free_property_content_recurse(prop, true, 0); prop->data.pointer = nullptr; prop->len = prop->totallen = 0; } void IDP_Reset(IDProperty *prop, const IDProperty *reference) { if (prop == nullptr) { return; } IDP_ClearProperty(prop); if (reference != nullptr) { IDP_MergeGroup(prop, reference, true); } } static void idp_foreach_property_recurse(IDProperty *id_property_root, const int type_filter, const int recursion_depth, const FunctionRef callback) { if (!id_property_root) { return; } if (recursion_depth > MAX_IDPROP_DEPTH_LEVEL) { CLOG_ERROR(&LOG, "Too deep level of IDProperties embedding detected (over %d levels), this is " "likely caused by a buggy script or add-on. The data in property '%s' will not " "be processed further", MAX_IDPROP_DEPTH_LEVEL, id_property_root->name); return; } if (type_filter == 0 || (1 << id_property_root->type) & type_filter) { callback(id_property_root); } /* Recursive call into container types of ID properties. */ switch (id_property_root->type) { case IDP_GROUP: { for (IDProperty &loop : id_property_root->data.group) { idp_foreach_property_recurse(&loop, type_filter, recursion_depth + 1, callback); } break; } case IDP_IDPARRAY: { IDProperty *loop = IDP_property_array_get(id_property_root); for (int i = 0; i < id_property_root->len; i++) { idp_foreach_property_recurse(&loop[i], type_filter, recursion_depth + 1, callback); } break; } default: break; /* Nothing to do here with other types of IDProperties... */ } } void IDP_foreach_property(IDProperty *id_property_root, const int type_filter, const FunctionRef callback) { idp_foreach_property_recurse(id_property_root, type_filter, 0, callback); } static void idp_blend_write_recurse(BlendWriter *writer, const IDProperty *prop, const int recursion_depth); static void IDP_WriteProperty_OnlyData(const IDProperty *prop, BlendWriter *writer, const int recursion_depth); static void write_ui_data(const IDProperty *prop, BlendWriter *writer) { IDPropertyUIData *ui_data = prop->ui_data; writer->write_string(ui_data->description); switch (IDP_ui_data_type(prop)) { case IDP_UI_DATA_TYPE_STRING: { IDPropertyUIDataString *ui_data_string = reinterpret_cast(ui_data); writer->write_string(ui_data_string->default_value); writer->write_struct_cast(ui_data); break; } case IDP_UI_DATA_TYPE_ID: { writer->write_struct_cast(ui_data); break; } case IDP_UI_DATA_TYPE_INT: { IDPropertyUIDataInt *ui_data_int = reinterpret_cast(ui_data); if (prop->type == IDP_ARRAY) { writer->write_int32_array(uint(ui_data_int->default_array_len), static_cast(ui_data_int->default_array)); } writer->write_struct_array(ui_data_int->enum_items_num, ui_data_int->enum_items); for (const int64_t i : IndexRange(ui_data_int->enum_items_num)) { IDPropertyUIDataEnumItem &item = ui_data_int->enum_items[i]; writer->write_string(item.identifier); writer->write_string(item.name); writer->write_string(item.description); } writer->write_struct_cast(ui_data); break; } case IDP_UI_DATA_TYPE_BOOLEAN: { IDPropertyUIDataBool *ui_data_bool = reinterpret_cast(ui_data); if (prop->type == IDP_ARRAY) { writer->write_int8_array(uint(ui_data_bool->default_array_len), const_cast(ui_data_bool->default_array)); } writer->write_struct_cast(ui_data); break; } case IDP_UI_DATA_TYPE_FLOAT: { IDPropertyUIDataFloat *ui_data_float = reinterpret_cast(ui_data); if (prop->type == IDP_ARRAY) { writer->write_double_array(uint(ui_data_float->default_array_len), ui_data_float->default_array); } writer->write_struct_cast(ui_data); break; } case IDP_UI_DATA_TYPE_UNSUPPORTED: { BLI_assert_unreachable(); break; } } } static void IDP_WriteArray(const IDProperty *prop, BlendWriter *writer, const int recursion_depth) { /* Remember to set #IDProperty.totallen to len in the linking code! */ if (prop->data.pointer) { /* Only write the actual data (`prop->len`), not the whole allocated buffer (`prop->totallen`). */ switch (eIDPropertyType(prop->subtype)) { case IDP_GROUP: { writer->write_pointer_array(uint32_t(prop->len), prop->data.pointer); IDProperty **array = static_cast(prop->data.pointer); for (int i = 0; i < prop->len; i++) { idp_blend_write_recurse(writer, array[i], recursion_depth + 1); } break; } case IDP_DOUBLE: writer->write_double_array(uint32_t(prop->len), static_cast(prop->data.pointer)); break; case IDP_INT: writer->write_int32_array(uint32_t(prop->len), static_cast(prop->data.pointer)); break; case IDP_FLOAT: writer->write_float_array(uint32_t(prop->len), static_cast(prop->data.pointer)); break; case IDP_BOOLEAN: writer->write_int8_array(uint32_t(prop->len), static_cast(prop->data.pointer)); break; case IDP_STRING: case IDP_ARRAY: case IDP_ID: case IDP_IDPARRAY: BLI_assert_unreachable(); break; } } } static void IDP_WriteIDPArray(const IDProperty *prop, BlendWriter *writer, const int recursion_depth) { /* Remember to set #IDProperty.totallen to len in the linking code! */ if (prop->data.pointer) { const IDProperty *array = static_cast(prop->data.pointer); /* Recursion depth limit also needs to be handled here, as IDP arrays are written in a single * call, without going through a call to `idp_blend_write_recurse`. */ if (recursion_depth > MAX_IDPROP_DEPTH_LEVEL_FOR_WRITE) { CLOG_ERROR(&LOG, "Too deep level of IDProperties embedding detected (over %d levels), this is " "likely caused by a buggy script or add-on. The data in property '%s' will not " "be written in the blendfile or memfile undo step", MAX_IDPROP_DEPTH_LEVEL_FOR_WRITE, prop->name); IDProperty *empty_prop_idparray = IDP_NewIDPArray(prop->name); IDP_ResizeIDPArray(empty_prop_idparray, prop->len); IDProperty *empty_array = static_cast(empty_prop_idparray->data.pointer); for (int a = 0; a < empty_prop_idparray->len; a++) { empty_array[a].type = IDP_INT; empty_array[a].subtype = 0; IDP_int_set(&empty_array[a], 0); STRNCPY(empty_array[a].name, array[a].name); } writer->write_struct_array_at_address( empty_prop_idparray->len, prop->data.pointer, empty_array); for (int a = 0; a < empty_prop_idparray->len; a++) { IDP_WriteProperty_OnlyData(&empty_array[a], writer, recursion_depth + 1); } IDP_FreeProperty_ex(empty_prop_idparray, false); return; } writer->write_struct_array(prop->len, array); for (int a = 0; a < prop->len; a++) { IDP_WriteProperty_OnlyData(&array[a], writer, recursion_depth + 1); } } } static void IDP_WriteString(const IDProperty *prop, BlendWriter *writer) { /* Remember to set #IDProperty.totallen to len in the linking code! */ /* Do not use #BlendWriter::write_string here, since 'bytes' sub-type of IDProperties may not be * null-terminated. */ writer->write_char_array(uint(prop->len), static_cast(prop->data.pointer)); } static void IDP_WriteGroup(const IDProperty *prop, BlendWriter *writer, const int recursion_depth) { for (IDProperty &loop : prop->data.group) { idp_blend_write_recurse(writer, &loop, recursion_depth + 1); } } /* Functions to read/write ID Properties */ static void IDP_WriteProperty_OnlyData(const IDProperty *prop, BlendWriter *writer, const int recursion_depth) { switch (prop->type) { case IDP_INT: case IDP_FLOAT: case IDP_DOUBLE: case IDP_BOOLEAN: case IDP_ID: break; case IDP_GROUP: IDP_WriteGroup(prop, writer, recursion_depth); break; case IDP_STRING: IDP_WriteString(prop, writer); break; case IDP_ARRAY: IDP_WriteArray(prop, writer, recursion_depth); break; case IDP_IDPARRAY: IDP_WriteIDPArray(prop, writer, recursion_depth); break; } if (prop->ui_data != nullptr) { write_ui_data(prop, writer); } } static void idp_blend_write_recurse(BlendWriter *writer, const IDProperty *prop, const int recursion_depth) { if (recursion_depth > MAX_IDPROP_DEPTH_LEVEL_FOR_WRITE) { CLOG_ERROR(&LOG, "Too deep level of IDProperties embedding detected (over %d levels), this is " "likely caused by a buggy script or add-on. The data in property '%s' will not " "be written in the blendfile or memfile undo step", MAX_IDPROP_DEPTH_LEVEL_FOR_WRITE, prop->name); IDProperty empty_prop = {}; empty_prop.type = IDP_INT; empty_prop.subtype = 0; IDP_int_set(&empty_prop, 0); STRNCPY(empty_prop.name, prop->name); writer->write_struct_at_address(prop, &empty_prop); IDP_WriteProperty_OnlyData(&empty_prop, writer, recursion_depth); return; } writer->write_struct(prop); IDP_WriteProperty_OnlyData(prop, writer, recursion_depth); } void IDP_BlendWrite(BlendWriter *writer, const IDProperty *prop) { idp_blend_write_recurse(writer, prop, 0); } static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader, const int recursion_depth); static void read_ui_data(IDProperty *prop, BlendDataReader *reader) { /* NOTE: null UI data can happen when opening more recent files with unknown types of * IDProperties. */ switch (IDP_ui_data_type(prop)) { case IDP_UI_DATA_TYPE_STRING: { BLO_read_struct(reader, IDPropertyUIDataString, &prop->ui_data); if (prop->ui_data) { IDPropertyUIDataString *ui_data_string = reinterpret_cast( prop->ui_data); BLO_read_string(reader, &ui_data_string->default_value); } break; } case IDP_UI_DATA_TYPE_ID: { BLO_read_struct(reader, IDPropertyUIDataID, &prop->ui_data); break; } case IDP_UI_DATA_TYPE_INT: { BLO_read_struct(reader, IDPropertyUIDataInt, &prop->ui_data); IDPropertyUIDataInt *ui_data_int = reinterpret_cast(prop->ui_data); if (prop->type == IDP_ARRAY) { BLO_read_array_and_validate_size( reader, &ui_data_int->default_array, &ui_data_int->default_array_len); } else { ui_data_int->default_array = nullptr; ui_data_int->default_array_len = 0; } BLO_read_array_and_validate_size( reader, &ui_data_int->enum_items, &ui_data_int->enum_items_num); for (const int64_t i : IndexRange(ui_data_int->enum_items_num)) { IDPropertyUIDataEnumItem &item = ui_data_int->enum_items[i]; BLO_read_string(reader, &item.identifier); BLO_read_string(reader, &item.name); BLO_read_string(reader, &item.description); } break; } case IDP_UI_DATA_TYPE_BOOLEAN: { BLO_read_struct(reader, IDPropertyUIDataBool, &prop->ui_data); IDPropertyUIDataBool *ui_data_bool = reinterpret_cast(prop->ui_data); if (prop->type == IDP_ARRAY) { BLO_read_array_and_validate_size( reader, &ui_data_bool->default_array, &ui_data_bool->default_array_len); } else { ui_data_bool->default_array = nullptr; ui_data_bool->default_array_len = 0; } break; } case IDP_UI_DATA_TYPE_FLOAT: { BLO_read_struct(reader, IDPropertyUIDataFloat, &prop->ui_data); IDPropertyUIDataFloat *ui_data_float = reinterpret_cast( prop->ui_data); if (prop->type == IDP_ARRAY) { BLO_read_array_and_validate_size( reader, &ui_data_float->default_array, &ui_data_float->default_array_len); } else { ui_data_float->default_array = nullptr; ui_data_float->default_array_len = 0; } break; } case IDP_UI_DATA_TYPE_UNSUPPORTED: { BLI_assert_unreachable(); /* Do not attempt to read unknown data. */ prop->ui_data = nullptr; break; } } if (prop->ui_data) { BLO_read_string(reader, &prop->ui_data->description); } } static void IDP_DirectLinkIDPArray(IDProperty *prop, BlendDataReader *reader, const int recursion_depth) { /* since we didn't save the extra buffer, set totallen to len */ prop->totallen = prop->len; BLO_read_array_and_validate_size( reader, reinterpret_cast(&prop->data.pointer), &prop->len); IDProperty *array = static_cast(prop->data.pointer); /* NOTE:, idp-arrays didn't exist in 2.4x, so the pointer will be cleared * there's not really anything we can do to correct this, at least don't crash */ if (array == nullptr) { prop->len = 0; prop->totallen = 0; } for (int i = 0; i < prop->len; i++) { IDP_DirectLinkProperty(&array[i], reader, recursion_depth + 1); } } static void IDP_DirectLinkArray(IDProperty *prop, BlendDataReader *reader, const int recursion_depth) { /* since we didn't save the extra buffer, set totallen to len */ prop->totallen = prop->len; switch (eIDPropertyType(prop->subtype)) { case IDP_GROUP: { BLO_read_pointer_array_and_validate_size(reader, &prop->data.pointer, &prop->len); IDProperty **array = static_cast(prop->data.pointer); for (int i = 0; i < prop->len; i++) { IDP_DirectLinkProperty(array[i], reader, recursion_depth + 1); } break; } case IDP_DOUBLE: BLO_read_array_and_validate_size( reader, reinterpret_cast(&prop->data.pointer), &prop->len); break; case IDP_INT: BLO_read_array_and_validate_size( reader, reinterpret_cast(&prop->data.pointer), &prop->len); break; case IDP_FLOAT: BLO_read_array_and_validate_size( reader, reinterpret_cast(&prop->data.pointer), &prop->len); break; case IDP_BOOLEAN: BLO_read_array_and_validate_size( reader, reinterpret_cast(&prop->data.pointer), &prop->len); break; case IDP_STRING: case IDP_ARRAY: case IDP_ID: case IDP_IDPARRAY: BLI_assert_unreachable(); break; } if (prop->data.pointer == nullptr) { prop->len = 0; prop->totallen = 0; } } static void IDP_DirectLinkString(IDProperty *prop, BlendDataReader *reader) { BLO_read_array_and_validate_size( reader, reinterpret_cast(&prop->data.pointer), &prop->len); /* Since we didn't save the extra string buffer, set totallen to len. */ prop->totallen = prop->len; } static void IDP_DirectLinkGroup(IDProperty *prop, BlendDataReader *reader, const int recursion_depth) { ListBaseT *lb = &prop->data.group; prop->data.children_map = nullptr; BLO_read_struct_list(reader, IDProperty, lb); if (!prop->data.group.is_empty()) { idp_group_children_map_ensure(*prop); } /* Link child id properties now. */ for (IDProperty &loop : prop->data.group) { IDP_DirectLinkProperty(&loop, reader, recursion_depth + 1); if (!prop->data.children_map->children.add(&loop)) { CLOG_WARN(&LOG, "duplicate ID property '%s' in group", loop.name); } } } static void IDP_DirectLinkProperty(IDProperty *prop, BlendDataReader *reader, const int recursion_depth) { auto reset_property = [](IDProperty *idprop) -> void { idprop->type = IDP_INT; idprop->subtype = 0; IDP_int_set(idprop, 0); idprop->ui_data = nullptr; }; if (recursion_depth > MAX_IDPROP_DEPTH_LEVEL_FOR_READ) { CLOG_ERROR(&LOG, "Too deep level of IDProperties embedding detected (over %d levels), this is " "likely caused by a buggy script or add-on. The data in property '%s' will not " "be read from the blendfile", MAX_IDPROP_DEPTH_LEVEL_FOR_READ, prop->name); /* NOTE: No attempt to free the property, as it may lead to further recursion. */ reset_property(prop); return; } switch (prop->type) { case IDP_GROUP: IDP_DirectLinkGroup(prop, reader, recursion_depth); break; case IDP_STRING: IDP_DirectLinkString(prop, reader); break; case IDP_ARRAY: IDP_DirectLinkArray(prop, reader, recursion_depth); break; case IDP_IDPARRAY: IDP_DirectLinkIDPArray(prop, reader, recursion_depth); break; case IDP_DOUBLE: /* NOTE: this is endianness-sensitive. */ /* Doubles are stored in the same field as `int val, val2` in the #IDPropertyData struct. * * In case of endianness switching, `val` and `val2` would have already been switched by the * generic reading code, so they would need to be first un-switched individually, and then * re-switched as a single 64-bit entity. */ break; case IDP_INT: case IDP_FLOAT: case IDP_BOOLEAN: case IDP_ID: break; /* Nothing special to do here. */ default: /* Unknown IDP type, nuke it (we cannot handle unknown types everywhere in code, * IDP are way too polymorphic to do it safely). */ CLOG_WARN(&LOG, "Found unknown IDProperty type %d, reset to Integer one with null value", prop->type); /* NOTE: we do not attempt to free unknown prop, we have no way to know how to do that! */ reset_property(prop); } if (prop->ui_data != nullptr) { read_ui_data(prop, reader); } } void IDP_BlendReadData_impl(BlendDataReader *reader, IDProperty **prop, const char *caller_func_id) { if (*prop) { if ((*prop)->type == IDP_GROUP) { IDP_DirectLinkGroup(*prop, reader, 0); } else { /* corrupt file! */ CLOG_WARN(&LOG, "%s: found non group data, freeing type %d!", caller_func_id, (*prop)->type); /* Don't risk it, data is likely corrupt. */ // idp_free_property_content_recurse(*prop, true, 0); *prop = nullptr; } } } eIDPropertyUIDataType IDP_ui_data_type(const IDProperty *prop) { if (prop->type == IDP_STRING) { return IDP_UI_DATA_TYPE_STRING; } if (prop->type == IDP_ID) { return IDP_UI_DATA_TYPE_ID; } if (prop->type == IDP_INT || (prop->type == IDP_ARRAY && prop->subtype == IDP_INT)) { return IDP_UI_DATA_TYPE_INT; } if (ELEM(prop->type, IDP_FLOAT, IDP_DOUBLE) || (prop->type == IDP_ARRAY && ELEM(prop->subtype, IDP_FLOAT, IDP_DOUBLE))) { return IDP_UI_DATA_TYPE_FLOAT; } if (prop->type == IDP_BOOLEAN || (prop->type == IDP_ARRAY && prop->subtype == IDP_BOOLEAN)) { return IDP_UI_DATA_TYPE_BOOLEAN; } return IDP_UI_DATA_TYPE_UNSUPPORTED; } bool IDP_ui_data_supported(const IDProperty *prop) { return IDP_ui_data_type(prop) != IDP_UI_DATA_TYPE_UNSUPPORTED; } static IDPropertyUIData *ui_data_alloc(const eIDPropertyUIDataType type) { switch (type) { case IDP_UI_DATA_TYPE_STRING: { IDPropertyUIDataString *ui_data = MEM_new(__func__); return &ui_data->base; } case IDP_UI_DATA_TYPE_ID: { IDPropertyUIDataID *ui_data = MEM_new(__func__); return &ui_data->base; } case IDP_UI_DATA_TYPE_INT: { IDPropertyUIDataInt *ui_data = MEM_new(__func__); ui_data->min = INT_MIN; ui_data->max = INT_MAX; ui_data->soft_min = INT_MIN; ui_data->soft_max = INT_MAX; ui_data->step = 1; return &ui_data->base; } case IDP_UI_DATA_TYPE_BOOLEAN: { IDPropertyUIDataBool *ui_data = MEM_new(__func__); return &ui_data->base; } case IDP_UI_DATA_TYPE_FLOAT: { IDPropertyUIDataFloat *ui_data = MEM_new(__func__); ui_data->min = -FLT_MAX; ui_data->max = FLT_MAX; ui_data->soft_min = -FLT_MAX; ui_data->soft_max = FLT_MAX; ui_data->step = 1.0f; ui_data->precision = 3; return &ui_data->base; } case IDP_UI_DATA_TYPE_UNSUPPORTED: { /* UI data not supported for remaining types, this shouldn't be called in those cases. */ BLI_assert_unreachable(); break; } } return nullptr; } IDPropertyUIData *IDP_ui_data_ensure(IDProperty *prop) { if (prop->ui_data != nullptr) { return prop->ui_data; } prop->ui_data = ui_data_alloc(IDP_ui_data_type(prop)); return prop->ui_data; } static IDPropertyUIData *convert_base_ui_data(IDPropertyUIData *src, const eIDPropertyUIDataType dst_type) { IDPropertyUIData *dst = ui_data_alloc(dst_type); *dst = *src; src->description = nullptr; return dst; } IDPropertyUIData *IDP_TryConvertUIData(IDPropertyUIData *src, const eIDPropertyUIDataType src_type, const eIDPropertyUIDataType dst_type) { switch (src_type) { case IDP_UI_DATA_TYPE_STRING: { switch (dst_type) { case IDP_UI_DATA_TYPE_STRING: return src; case IDP_UI_DATA_TYPE_INT: case IDP_UI_DATA_TYPE_BOOLEAN: case IDP_UI_DATA_TYPE_FLOAT: case IDP_UI_DATA_TYPE_ID: { IDPropertyUIData *dst = convert_base_ui_data(src, dst_type); ui_data_free(src, src_type); return dst; } case IDP_UI_DATA_TYPE_UNSUPPORTED: break; } break; } case IDP_UI_DATA_TYPE_ID: { switch (dst_type) { case IDP_UI_DATA_TYPE_ID: return src; case IDP_UI_DATA_TYPE_STRING: case IDP_UI_DATA_TYPE_INT: case IDP_UI_DATA_TYPE_BOOLEAN: case IDP_UI_DATA_TYPE_FLOAT: { IDPropertyUIData *dst = convert_base_ui_data(src, dst_type); ui_data_free(src, src_type); return dst; } case IDP_UI_DATA_TYPE_UNSUPPORTED: break; } break; } case IDP_UI_DATA_TYPE_INT: { IDPropertyUIDataInt *src_int = reinterpret_cast(src); switch (dst_type) { case IDP_UI_DATA_TYPE_INT: return src; case IDP_UI_DATA_TYPE_ID: case IDP_UI_DATA_TYPE_STRING: { IDPropertyUIData *dst = convert_base_ui_data(src, dst_type); ui_data_free(src, src_type); return dst; } case IDP_UI_DATA_TYPE_BOOLEAN: { IDPropertyUIDataBool *dst = reinterpret_cast( convert_base_ui_data(src, dst_type)); dst->default_value = src_int->default_value != 0; if (src_int->default_array) { dst->default_array = MEM_new_array_uninitialized( size_t(src_int->default_array_len), __func__); for (int i = 0; i < src_int->default_array_len; i++) { dst->default_array[i] = src_int->default_array[i] != 0; } } ui_data_free(src, src_type); return &dst->base; } case IDP_UI_DATA_TYPE_FLOAT: { IDPropertyUIDataFloat *dst = reinterpret_cast( convert_base_ui_data(src, dst_type)); dst->min = double(src_int->min); dst->max = double(src_int->max); dst->soft_min = double(src_int->soft_min); dst->soft_max = double(src_int->soft_max); dst->step = float(src_int->step); dst->default_value = double(src_int->default_value); if (src_int->default_array) { dst->default_array = MEM_new_array_uninitialized( size_t(src_int->default_array_len), __func__); for (int i = 0; i < src_int->default_array_len; i++) { dst->default_array[i] = double(src_int->default_array[i]); } } ui_data_free(src, src_type); return &dst->base; } case IDP_UI_DATA_TYPE_UNSUPPORTED: break; } break; } case IDP_UI_DATA_TYPE_BOOLEAN: { IDPropertyUIDataBool *src_bool = reinterpret_cast(src); switch (dst_type) { case IDP_UI_DATA_TYPE_BOOLEAN: return src; case IDP_UI_DATA_TYPE_ID: case IDP_UI_DATA_TYPE_STRING: { IDPropertyUIData *dst = convert_base_ui_data(src, dst_type); ui_data_free(src, src_type); return dst; } case IDP_UI_DATA_TYPE_INT: { IDPropertyUIDataInt *dst = reinterpret_cast( convert_base_ui_data(src, dst_type)); dst->min = 0; dst->max = 1; dst->soft_min = 0; dst->soft_max = 1; dst->step = 1; dst->default_value = int(src_bool->default_value); if (src_bool->default_array) { dst->default_array = MEM_new_array_uninitialized( size_t(src_bool->default_array_len), __func__); for (int i = 0; i < src_bool->default_array_len; i++) { dst->default_array[i] = int(src_bool->default_array[i]); } } ui_data_free(src, src_type); return &dst->base; } case IDP_UI_DATA_TYPE_FLOAT: { IDPropertyUIDataFloat *dst = reinterpret_cast( convert_base_ui_data(src, dst_type)); dst->min = 0.0; dst->max = 1.0; dst->soft_min = 0.0; dst->soft_max = 1.0; dst->step = 1.0; if (src_bool->default_array) { dst->default_array = MEM_new_array_uninitialized( size_t(src_bool->default_array_len), __func__); for (int i = 0; i < src_bool->default_array_len; i++) { dst->default_array[i] = src_bool->default_array[i] == 0 ? 0.0 : 1.0; } } ui_data_free(src, src_type); return &dst->base; } case IDP_UI_DATA_TYPE_UNSUPPORTED: break; } break; } case IDP_UI_DATA_TYPE_FLOAT: { IDPropertyUIDataFloat *src_float = reinterpret_cast(src); switch (dst_type) { case IDP_UI_DATA_TYPE_FLOAT: return src; case IDP_UI_DATA_TYPE_ID: case IDP_UI_DATA_TYPE_STRING: return convert_base_ui_data(src, dst_type); case IDP_UI_DATA_TYPE_INT: { auto clamp_double_to_int = [](const double value) { return int(std::clamp(value, INT_MIN, INT_MAX)); }; IDPropertyUIDataInt *dst = reinterpret_cast( convert_base_ui_data(src, dst_type)); dst->min = clamp_double_to_int(src_float->min); dst->max = clamp_double_to_int(src_float->max); dst->soft_min = clamp_double_to_int(src_float->soft_min); dst->soft_max = clamp_double_to_int(src_float->soft_max); dst->step = clamp_double_to_int(src_float->step); dst->default_value = clamp_double_to_int(src_float->default_value); if (src_float->default_array) { dst->default_array = MEM_new_array_uninitialized( size_t(src_float->default_array_len), __func__); for (int i = 0; i < src_float->default_array_len; i++) { dst->default_array[i] = clamp_double_to_int(src_float->default_array[i]); } } ui_data_free(src, src_type); return &dst->base; } case IDP_UI_DATA_TYPE_BOOLEAN: { IDPropertyUIDataBool *dst = reinterpret_cast( convert_base_ui_data(src, dst_type)); dst->default_value = src_float->default_value > 0.0f; if (src_float->default_array) { dst->default_array = MEM_new_array_uninitialized( size_t(src_float->default_array_len), __func__); for (int i = 0; i < src_float->default_array_len; i++) { dst->default_array[i] = src_float->default_array[i] > 0.0f; } } ui_data_free(src, src_type); return &dst->base; } case IDP_UI_DATA_TYPE_UNSUPPORTED: break; } break; } case IDP_UI_DATA_TYPE_UNSUPPORTED: break; } ui_data_free(src, src_type); return nullptr; } namespace bke::idprop { void hash(const IDProperty &base_prop, XXH3_state_t *hash_state) { IDP_foreach_property(&const_cast(base_prop), 0, [&](const IDProperty *prop) { XXH3_64bits_update(hash_state, prop->name, strlen(prop->name)); XXH3_64bits_update(hash_state, &prop->type, sizeof(prop->type)); switch (eIDPropertyType(prop->type)) { case IDP_INT: case IDP_FLOAT: case IDP_BOOLEAN: { XXH3_64bits_update(hash_state, &prop->data.val, sizeof(prop->data.val)); break; } case IDP_DOUBLE: { const double val = IDP_double_get(prop); XXH3_64bits_update(hash_state, &val, sizeof(double)); break; } case IDP_STRING: { if (prop->data.pointer) { XXH3_64bits_update(hash_state, prop->data.pointer, size_t(prop->len)); } break; } case IDP_ARRAY: { if (prop->data.pointer) { XXH3_64bits_update(hash_state, &prop->subtype, sizeof(prop->subtype)); const size_t elem_size = idp_size_table[int(prop->subtype)]; XXH3_64bits_update(hash_state, prop->data.pointer, elem_size * size_t(prop->len)); } break; } case IDP_ID: { /* NOTE: Hashing the session_uid of the ID makes the hash session-specific. An alternative * hash wouldn't be complete though, because data-block names aren't necessarily unique. */ if (const ID *id = static_cast(prop->data.pointer)) { XXH3_64bits_update(hash_state, &id->session_uid, sizeof(ID::session_uid)); } break; } case IDP_GROUP: case IDP_IDPARRAY: { /* Handled by the recursion in #IDP_foreach_property. */ break; } } }); } } // namespace bke::idprop /** \} */ /* -------------------------------------------------------------------- */ /** \name Debugging * \{ */ #ifndef NDEBUG const IDProperty *_IDP_assert_type(const IDProperty *prop, const char ty) { BLI_assert(prop->type == ty); return prop; } const IDProperty *_IDP_assert_type_and_subtype(const IDProperty *prop, const char ty, const char sub_ty) { BLI_assert((prop->type == ty) && (prop->subtype == sub_ty)); return prop; } const IDProperty *_IDP_assert_type_mask(const IDProperty *prop, const int ty_mask) { BLI_assert(1 << int(prop->type) & ty_mask); return prop; } #endif /* !NDEBUG */ /** \} */ } // namespace blender